Typescript 包装联合类型的对象并关联类型化元数据

Typescript 包装联合类型的对象并关联类型化元数据,typescript,Typescript,我们有一个包含配置数据的资产库,该库公开了可跨进程共享的只读资产 我们有一个独立的系统部分,它应该能够获取这些资产并为它们存储覆盖的配置数据。由于此系统的工作方式,此进程应将此重写数据保存在资产旁边,而不应向资产写入任何内容 下面,我已经写了一些基本的打字脚本来说明我们正在尝试做什么,并说明它在哪里不能像我们预期的那样工作 LibraryObjectTypeA和LibraryObjectTypeB是位于库中的资产。实际上,有许多资产。这些资产的所有类型的集合都由联合类型“LibraryObjec

我们有一个包含配置数据的资产库,该库公开了可跨进程共享的只读资产

我们有一个独立的系统部分,它应该能够获取这些资产并为它们存储覆盖的配置数据。由于此系统的工作方式,此进程应将此重写数据保存在资产旁边,而不应向资产写入任何内容

下面,我已经写了一些基本的打字脚本来说明我们正在尝试做什么,并说明它在哪里不能像我们预期的那样工作

LibraryObjectTypeA和LibraryObjectTypeB是位于库中的资产。实际上,有许多资产。这些资产的所有类型的集合都由联合类型“LibraryObjects”捕获。它们是使用getOneOfLibraryObjects()方法从库中检索的

我们希望能够使用这种联合类型优雅地表示一个新对象(容器),该对象可以容纳我们正在使用的资产,并在资产旁边存储类型化的配置数据

下面的代码没有按预期工作(我已经突出显示了错误发生的位置)。使其工作的唯一方法是显式地将资产强制转换为特定的资产类型,而不仅仅是联合的成员。实际上,这对我们不起作用,因为欧盟内部有太多的资产类型

我们在做一些根本错误的事情吗?任何帮助都将不胜感激。谢谢大家!

// some objects I have which contain a bunch of data
type LibraryObjectTypeA = { a: number, configuration: { foo: number } }
type LibraryObjectTypeB = { b: number, configuration: { foo: number } }

type LibraryObjects = LibraryObjectTypeA | LibraryObjectTypeB

// a helpful function which returns one of these objects (in practice, this method is from a class which holds a catalog of these kinds of objects)
function getOneOfTheLibraryObjects() : LibraryObjects {
    // returning a LibraryObjectTypeA just as an example
    return {
        b: 1,
        configuration: { foo: 1 }
    }
}

// get one of these objects 
const libraryObject = getOneOfTheLibraryObjects()

// this is from some other code which needs to hold onto these objects and add some metadata to them, these
// objects are part of an asset library which is shared across lots of parts of the application, so we want 
// this overridden configuration data to live outside the actual objects
type Container = {
    [key: string]: {
        libraryObject: LibraryObjectTypeA
        overriddenConfig: Partial<LibraryObjectTypeA['configuration']>
    } | {
        libraryObject: LibraryObjectTypeB
        overriddenConfig: Partial<LibraryObjectTypeB['configuration']>
    }
}
// for simplicity, and to test, I have been verbose with the type definition above, in practice it is built like this:
// export type ConfigOverride<T extends LibraryObjects> = T extends LibraryObjects ? {
//   libraryObject: T
//   overriddenConfig: Partial<T['configuration']>
// } : never
// export type Container = {
//   [key: string]: ConfigOverride<LibraryObjects>
// }


// create a place to hold these objects
const container : Container = {}


// some cast functions to (arbitrarily?) break the union apart
export function isLibraryObjectTypeA(obj: any): obj is LibraryObjectTypeA {
  return true
}
export function isLibraryObjectTypeB(obj: any): obj is LibraryObjectTypeB {
  return true
}

// this does not work, because libraryObject is the union type 'LibraryObjects' and not explicetly known as LibraryObjectTypeA or LibraryObjectTypeB 
// in practice, this union type contains many of these types of objects, so we would really like to use the union type and not have a long list of conditionals
// to effectively cast into this container
container['key'] = {
    libraryObject: libraryObject,
    overriddenConfig: {
        foo: 2
    }
}

// however, if i assert it is one of the types, then I can add it
if (isLibraryObjectTypeA(libraryObject)) {
    container['key'] = {
        libraryObject: libraryObject,
        overriddenConfig: {
            foo: 2
        }
    }
}
else if (isLibraryObjectTypeB(libraryObject)) {
    container['key'] = {
        libraryObject: libraryObject,
        overriddenConfig: {
            foo: 2
        }
    }
}
//我的一些对象包含一组数据
类型LibraryObjectTypeA={a:number,配置:{foo:number}
类型LibraryObjectTypeB={b:number,配置:{foo:number}
类型LibraryObjects=LibraryObjectTypeA | LibraryObjectTypeB
//一个有用的函数,返回这些对象中的一个(在实践中,此方法来自一个包含这些类型对象的目录的类)
函数getOneOfLibraryObjects():LibraryObjects{
//返回LibraryObjectTypeA只是一个示例
返回{
b:1,
配置:{foo:1}
}
}
//获取其中一个对象
const libraryObject=getoneoflibraryobjects()
//这是来自其他一些需要保留这些对象并向其添加元数据的代码,这些
//对象是跨应用程序的许多部分共享的资产库的一部分,因此我们需要
//这将覆盖配置数据,使其位于实际对象之外
类型容器={
[键:字符串]:{
libraryObject:LibraryObjectTypeA
重写配置:部分
} | {
libraryObject:LibraryObjectTypeB
重写配置:部分
}
}
//为了简单起见,为了测试,我已经详细介绍了上面的类型定义,实际上它是这样构建的:
//导出类型ConfigOverride=T扩展LibraryObjects?{
//图书馆对象:T
//重写配置:部分
//}:从来没有
//导出类型容器={
//[键:字符串]:配置覆盖
// }
//创建一个放置这些对象的位置
常量容器:容器={}
//一些cast函数(任意地)将联盟分开
导出函数isLibraryObjectTypeA(obj:any):obj是LibraryObjectTypeA{
返回真值
}
导出函数isLibraryObjectTypeB(obj:any):obj是LibraryObjectTypeB{
返回真值
}
//这不起作用,因为libraryObject是联合类型“LibraryObjects”,而不是LibraryObjectTypeA或LibraryObjectTypeB
//实际上,这种联合类型包含许多这样的对象类型,因此我们确实希望使用联合类型,而不是有一个长长的条件列表
//要有效地放入这个容器中
容器['key']={
libraryObject:libraryObject,
覆盖配置:{
傅:2
}
}
//但是,如果我断言它是其中一种类型,那么我可以添加它
if(isLibraryObjectTypeA(libraryObject)){
容器['key']={
libraryObject:libraryObject,
覆盖配置:{
傅:2
}
}
}
else if(isLibraryObjectTypeB(libraryObject)){
容器['key']={
libraryObject:libraryObject,
覆盖配置:{
傅:2
}
}
}

是的,这看起来像是我一直在调用的,TS并没有很好地支持它。一般来说,我倾向于使用类型断言,因为我能想到的唯一类型安全的替代方法是冗余的。喜欢如果我不能很快想出更好的办法,我可能会发布一个关于这种效果的答案。