Say I have an object like:
const obj = {
a: 1,
"b?": 2,
"c?": 3
}
Its type currently is:
type Obj = {
a: number;
"b?": number;
"c?": number;
}
Is there a way to create a type that accepts obj and converts its type to:
type Obj = {
a: number;
b?: number;
c?: number;
}
Essentially making b and c optional.
CodePudding user response:
You can do it by using template literal types and inference. Breaking it into parts (but we'll combine them at the end):
First we want to get the required properties:
type MapRequiredKeys<T> = { [Key in keyof T as Key extends `${string}?` ? never : Key]: T[Key]; };There, if
Keymatches the pattern${string}?, we leave it out by usingneveras the key; otherwise, we use the key. Using that ontypeof objgives us{ a: number; }.Then we want the optional ones, and we want just the base part of the key, not the
?. That's whereinfercomes in:type MapOptionalKeys<T> = { [Key in keyof T as Key extends `${infer BaseKey}?` ? BaseKey : never]?: T[Key]; };We match
Keyagainst${infer BaseKey}?and then useBaseKeyif it matches (leaving out the property entirely if it doesn't). We use?on it to make the property optional.Combining them:
type MapKeys<T> = MapRequiredKeys<T> & MapOptionalKeys<T>; type ObjType = MapKeys<typeof obj>;
But we can combine them into a single type, which makes the hints that TypeScript gives us clearer:
type MapKeys<T> = {
[Key in keyof T as Key extends `${string}?` ? never : Key]: T[Key];
} & {
[Key in keyof T as Key extends `${infer BaseKey}?` ? BaseKey : never]?: T[Key];
};
type ObjType = MapKeys<typeof obj>;
If you hover ObjType there, you see type ObjType = { a: number; } & { b?: number | undefined, c?: number | undefined; } instead of seeing the "calls" to MapRequiredKeys and MapOptionalKeys.
