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
Key
matches the pattern${string}?
, we leave it out by usingnever
as the key; otherwise, we use the key. Using that ontypeof obj
gives us{ a: number; }
.Then we want the optional ones, and we want just the base part of the key, not the
?
. That's whereinfer
comes in:type MapOptionalKeys<T> = { [Key in keyof T as Key extends `${infer BaseKey}?` ? BaseKey : never]?: T[Key]; };
We match
Key
against${infer BaseKey}?
and then useBaseKey
if 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
.