【问题标题】:TypeScript definition to merge objects, but prevent the merging of different types in the object propertiesTypeScript 定义合并对象,但防止对象属性中不同类型的合并
【发布时间】:2020-07-31 12:21:45
【问题描述】:

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

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

/** 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 Playground Link

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

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

【问题讨论】:

  • 看看你的函数会很好(因为你知道如何编写它)而且通常编译器会正确推断出返回类型,所以这通常不是棘手的部分。从您的示例中不清楚您期望的输出是什么,因为 bot srcext 共享相同的道具名称 a
  • @DamianGreen 感谢您的回复。我希望最后的编辑可以帮助您理解我的问题。提到的功能取决于许多其他代码。在发布之前,我必须大大缩短代码。但也许这个编辑已经有所帮助。如果没有,请告诉我。
  • SafelyMergedObject 是一个函数吗?这是它的一个对象
  • @DamianGreen SafelyMergedObject 类型应该是描述合并对象的泛型类型。
  • 您是否正在寻找this?如果源属性类型存在,您似乎总是使用它,但如果没有更多用例,就很难判断。您可以尝试链接类型并查看它是否有效或中断,然后更新您的问题吗?如果可行,我会写下来。

标签: typescript types


【解决方案1】:

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

这被翻译成类似的东西:

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&lt;U, keyof T&gt;,这是T中不存在的U的属性。然后我们遍历T 的键,如果属性不在U 中,或者如果它在U 但至少T[K]U[K] 中的一个不是目的。

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

让我们看看你的 srcext 值的作用:

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

如果您使用 IntelliSense 将鼠标悬停在上面,您会看到:

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&lt;T, U&gt;,可能会出现各种边缘情况。您需要决定在这些情况下您希望输出看起来像什么,并可能调整定义以实现这些目标。所以要小心。

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

Playground link to code

【讨论】:

    猜你喜欢
    • 2022-06-11
    • 1970-01-01
    • 2022-12-09
    • 2019-10-06
    • 2020-05-03
    • 2022-07-20
    • 2015-08-23
    • 2020-04-14
    • 1970-01-01
    相关资源
    最近更新 更多