You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

改进深度嵌套属性类型的泛型推导机制

问题描述

我有一个能正确推导深度嵌套属性类型的泛型函数,但只有传入根对象时才生效。能不能改进这个函数,让它不用传入根对象(比如通过泛型参数定义对象类型)也能正常工作?

以下是可运行与不可运行的代码示例:

// WORKING PART

type Flatten<T, O = never> = Writable<Cleanup<T>, O> extends infer U ?
    U extends O ? U : U extends object ?
    ValueOf<{ [K in keyof U]-?: (x: PrefixKeys<Flatten<U[K], O>, K, O>) => void }>
    | ((x: U) => void) extends (x: infer I) => void ?
    { [K in keyof I]: I[K] } : never : U : never;

type Writable<T, O> = T extends O ? T : {
    [P in keyof T as IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, P>]: T[P]
}

type IfEquals<X, Y, A = X, B = never> =
    (<T>() => T extends X ? 1 : 2) extends
    (<T>() => T extends Y ? 1 : 2) ? A : B;

type Cleanup<T> =
    0 extends (1 & T) ? unknown :
    T extends readonly any[] ?
    (Exclude<keyof T, keyof any[]> extends never ?
        { [k: `${number}`]: T[number] } : Omit<T, keyof any[]>) : T;

type PrefixKeys<V, K extends PropertyKey, O> =
    V extends O ? { [P in K]: V } : V extends object ?
    { [P in keyof V as
        `${Extract<K, string | number>}.${Extract<P, string | number>}`]: V[P] } :
    { [P in K]: V };

type ValueOf<T> = T[keyof T]

function lookup<T, K extends keyof Flatten<T>>(obj: T, key: K): Flatten<T>[K];
function lookup(obj: any, key: string) {
    const i = key.indexOf(".");
    return (i < 0) ? obj[key] : (lookup as any)(obj[key.substring(0, i)], key.substring(i + 1));
}

function lookupWithValue<ObjectType, DeepKeyType extends keyof Flatten<ObjectType>>(obj: ObjectType, key: DeepKeyType, value: Flatten<ObjectType>[DeepKeyType]): void {
    // SOME LOGIC
}

// SHOWCASE

const foo = {
    a: {
        b: {
            c: 10
        }
    },
    d: [
        0,
        {
            e: {
                f: "f"
            }
        },
        "20"
    ]
};

type FooType = typeof foo;

lookupWithValue(foo, "a.b.c", 10); // OK! Correct, because (number type == number type)
lookupWithValue(foo, "a.b.c", "10"); // ERROR! Correct, because (string type != number type)

// =====
// NOT WORKING PART
// =====

// Is there a way to create a method, which doesn't require the root object to be passed into ???
function lookupWithValueWithoutObject<ObjectType, ...>(key: ???, value: ???): void {
    // SOME LOGIC
}

lookupWithValueWithoutObject<FooType>("a.b.c", 10); // OK
lookupWithValueWithoutObject<FooType>("a.b.c", "10"); // ERROR

我尝试移除obj参数后,类型推导失效了——要么编译不通过,要么无法正确识别传入值和深度嵌套属性的类型不匹配错误。

解决方案

完全可以实现这个需求,只需要调整泛型参数的约束逻辑,让函数直接通过显式传入的对象类型泛型来推导嵌套属性的类型,无需依赖根对象参数。

修改后的函数代码如下:

function lookupWithValueWithoutObject<
  ObjectType,
  DeepKeyType extends keyof Flatten<ObjectType>
>(
  key: DeepKeyType,
  value: Flatten<ObjectType>[DeepKeyType]
): void {
  // SOME LOGIC
}

调用时只需显式指定对象类型泛型,TypeScript就能自动完成键的合法性校验和值的类型匹配:

lookupWithValueWithoutObject<FooType>("a.b.c", 10); // ✅ 类型匹配,正常通过
lookupWithValueWithoutObject<FooType>("a.b.c", "10"); // ❌ 类型不匹配,触发错误

核心原理

原函数是通过obj参数让TypeScript自动推断ObjectType,现在我们将ObjectType改为显式传入的泛型参数,后续的DeepKeyType依然基于Flatten<ObjectType>的键集合做约束,value的类型也依然关联Flatten<ObjectType>[DeepKeyType],因此类型校验逻辑完全保留,只是把类型推断的触发方式从参数推导改成了显式泛型传入。

需要注意的是,调用时必须显式指定ObjectType泛型(比如<FooType>),因为没有根对象参数可供TypeScript自动推断这个类型。

内容的提问来源于stack exchange,提问作者Mark Dolbyrev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 23:20:40