繁体   English   中英

TypeScript 定义合并对象,但在 object 属性中防止合并不同类型

[英]TypeScript definition to merge objects, but prevent the merging of different types in the object properties

我想写一个 function 扩展一个 object 和另一个 object。 例如:我们将调用一个 object src和另一个ext function 必须返回(副本) src object 但深度(递归)扩展 object 与ext objectAC 如果ext的一个(子)属性的数据类型与src (子)属性的类型不匹配,则 function 必须忽略ext值。 来自ext的新属性(不存在于src中)将被添加到结果 object 中。

为了更好地理解这里一个完整的例子:

/** A helper definition to build indexable objects */
interface indexObject<T> {
    [key: string]: T
}

type SafelyMergedObject<Src extends indexObject<any>, Ext extends indexObject<any>> = {
    // The searched type definition
}

const src= {
    a: "a",
    b: "b",
    c: false,
    d: {
        a: "Alice",
        b: "Bob",
    }
}
const ext = {
    a: ["a"],
    b: 1,
    c: true,
    d: {
        a: "Ann",
        c: "Clair",
    },
    e: "New value",
}
const result: SafelyMergedObject<typeof src, typeof ext> = {
    a: "a", /** Only string should be allowed here because
                it's the property type of the source (Src) type */
    b: "b", /** Same as above (`a` property) */
    c: true,/** Since `c` has the same data type the function
                should return the property value of the `ext`
                object */
    d: {    /** Same data type in both objects */
        a: "Ann",   /** new value from `ext` */
        b: "Bob",   /** copied value from `src` */
        c: "Clair", /** new property from `ext` */
    },
    e: "New Value", /** new property from `ext` */
}

TypeScript 游乐场链接

我知道如何编写 function。 这很容易,但我不知道如何编写这种类型定义。 这甚至可能吗?

默认的 TypeScript 类型推断行为不适合我的问题,因为类型是递归的,并且比简单的object类型更复杂。 例如,我将使用 function 将用户特定的配置加载到我的应用程序中。 用户配置可能已损坏。 所以我必须将默认配置与用户特定配置合并。

我将您的要求解释如下:对于 object 类型TU , object 类型SafelyMergedObject<T, U>应该具有与T & U相同的键,但与属性类型有一些差异。 如果键K仅存在于TU中,但不存在于两者中,则按原样使用该属性(因此这与T & U相同)。 如果键KTU中都存在,并且这两种属性类型中的至少一种不是 object,则使用T中的属性类型并忽略U中的属性。 如果键KTU中都存在,并且在TU都是object 类型,则通过SafelyMergedObject<T[K], U[K]>向下递归到该属性。

这被翻译成如下内容:

type SafelyMergedObject<T, U> = (
    Omit<U, keyof T> & { [K in keyof T]:
        K extends keyof U ? (
            [U[K], T[K]] extends [object, object] ?
            SafelyMergedObject<T[K], U[K]>
            : T[K]
        ) : T[K] }
) extends infer O ? { [K in keyof O]: O[K] } : never;

这里我们首先输出Omit<U, keyof T> ,这是 U 中不存在于T中的属性。 然后我们遍历T的键,如果属性不在U中,或者如果它在U中但T[K]U[K]中的至少一个不是 object,则输出T[K]

这里唯一的“技巧”是extends infer O? {[K in keyof O]: O[K]}: never extends infer O? {[K in keyof O]: O[K]}: never 所有这一切都是通过迭代所有键并将结果合并到单个 object 类型来“美化”或“扩展” object 类型。

让我们看看它与您的srcext值的作用:

type Result = SafelyMergedObject<typeof src, typeof ext>;

如果您使用 IntelliSense 超过 hover,您将看到:

type Result = {
    e: string;
    a: string;
    b: string;
    c: boolean;
    d: {
        c: string;
        a: string;
        b: string;
    };
}

我想,这就是你想要的。 请注意,如果我没有包含extends infer O...行,则Result类型将被评估为:

type Result = Pick<{
    a: string[];
    b: number;
    c: boolean;
    d: {
        a: string;
        c: string;
    };
    e: string;
}, "e"> & {
    a: string;
    b: string;
    c: boolean;
    d: SafelyMergedObject<{
        a: string;
        b: string;
    }, {
        a: string;
        c: string;
    }>;
}

这明显更难理解,尽管它属于同一类型。


请注意,如果您在不同情况下使用上述SafelyMergedObject<T, U> ,可能会出现各种边缘情况。 您需要确定您希望 output 在这些情况下的样子,并可能调整定义以实现这些目标。 所以要小心。

好的,希望有帮助; 祝你好运!

Playground 代码链接

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM