打字稿部分键映射

Typescript partial key mapping

我正在尝试创建一个通用类型,该类型将使用模板文字映射键。一般来说,我只想从逗号分隔的键列表中创建一个嵌套类型:

type TFoobar = "address" | "year" | "owner.age" | "owner.address.street";

const foobar: DotToType<TFooBar> = {
    address: true,
    owner: {
        age: true,
        address: {
            street: true
        }
    }
}

我试过这样实现它:

type DotToType<keys extends string> = {
    [Key in keys]: Key extends `${infer Parent}.${infer Leaf}`
        ? {[key in Parent]: DotToType<Leaf>}
        : {[k in Key]: boolean}
}[keys];

虽然它确实有一个正确的类型,但它使属性成为可选的(至少需要一个,但没有任何东西强制它们都存在)

const foobar: DotToType<TFooBar> = {
    address: true,
}; //valid, shouldnt be

const foobar: DotToType<TFooBar> = {
    owner: {
        age: true
    },
}; //valid, shouldnt be

const foobar: DotToType<TFooBar> = {
    address: true,
    year: false,
    owner: {
        age: true,
        address: {
            street: true
        }
    }
}; //valid

我也试过这样做:

type DotToType<keys extends string> = {
    [Key in keys as Key extends `${infer Parent}.${infer Leaf}` ? Parent : Key]:
    Key extends `${infer Parent}.${infer Leaf}` ? DotToType<Leaf> : boolean
};

虽然它确实强制执行字段,但如果我对同一个对象有多个路径,它就会停止工作。

const foobar: DotToType<"address" | "year" | "owner.address.street"> = {
    "address": true,
    "year": true,
    owner: {
        address: {
            street: true
        }
    }
}; // this works fine


const foobar: DotToType<"address" | "year" | "owner.address.street" | "owner.age"> = {
    "address": true,
    "year": true,
    owner: {
        address: {
            street: true
        }
    }
}; // this only allows the first `owner.{Leaf}`

您的初始代码生成联合,这就是为什么属性是可选的:

type DotToType<keys extends string> = {
    [Key in keys]: Key extends `${infer Parent}.${infer Leaf}`
        ? {[key in Parent]: DotToType<Leaf>}
        : {[k in Key]: boolean}
}[keys];

type MyType = DotToType<TFoobar>

// produces:

type MyType = {
    address: boolean;
} | {
    year: boolean;
} | {
    owner: {
        age: boolean;
    };
} | {
    owner: {
        address: {
            street: boolean;
        };
    };
}

您可以使用以下内容:

type TFoobar = "address" | "year" | "owner.age" | "owner.address.street";

type BeforeDot<K extends string> = K extends `${infer Parent}.${infer _}` ? Parent : K;
type ChildrenAfterDot<K extends string, P extends string> = K extends `${P}.${infer $Leaf}` ? $Leaf : never;


type DotToType<keys extends string> = {
    [Key in BeforeDot<keys>]: Key extends keys
        ? boolean
        : DotToType<ChildrenAfterDot<keys, Key>>
};

type MyType = DotToType<TFoobar>

// produces: 
type MyType = {
    address: boolean;
    year: boolean;
    owner: DotToType<"age" | "address.street">;
}

Playground link