Extract object type with optional fields in TypeScript
1type GetOptional<T> = any; // implementation23type cases = [4 Expect<Equal<GetOptional<{ foo: number; bar?: string }>, { bar?: string }>>,5 Expect<Equal<GetOptional<{ a: undefined; b?: undefined }>, { b?: undefined }>>6];
Today we discuss GetOptional
Here we consider multiple approaches including new possibilities from TypeScript 4.1
Let's have a look ⤵️
Pick only optional keys
First things first, we split the solution into 2 parts.
1type OptionalKeys<T> = keyof T;23type GetOptional<T> = Pick<T, OptionalKeys<T>>;
Let's implement OptionalKeys
properly as now it returns all the keys from the object type.
If we have an optional key, we can skip the definition of it in the object. It means that given the object with only one optional key, it's allowed to assign empty object to it.
1type WithOptional = { a?: string };2type WithRequired = { a: string };34// ✅ allowed5let obj1: WithOptional = {};6// @ts-expect-error ❌ Property 'a' is missing in type '{}' but required in type 'WithRequired'7let obj2: WithRequired = {};
Knowing that, we can come up with the conditional type {} extends Pick<T, K> ? T[K] : never
:
1type OptionalKeys<T> = keyof {2 [K in keyof T]: {} extends Pick<T, K> ? T[K] : never;3};45type GetOptional<T> = Pick<T, OptionalKeys<T>>;
Let's check the solution in Playground – https://tsplay.dev/wenOaN
If we check it on any object type, we will see that it's not working correct
1// "a" | "b"2type Test1 = OptionalKeys<{ a?: 1; b: 2 }>;
But this is because we use keyof { [K in keyof T]: ... }
which literally means keyof T
. The conditional type is right but we need to return only optional keys. Let's change the solution slightly to make it work.
Return only optional keys
Let's change the mapped type a bit:
1type Values<T> = T[keyof T];23type OptionalKeys<T> = Values<{4 [K in keyof T]: {} extends Pick<T, K> ? K : never;5}>;67type GetOptional<T> = Pick<T, OptionalKeys<T>>;
What we did here:
- We added
Values
which returns not keys but values of the object type - In
OptionalKeys
we now return keyK
instead of valueT[K]
. In combination withValues
we can get the optional keys ofT
The solution now works as expected https://tsplay.dev/WzL1rN ✅
Is that it? Not really, let's look for the shorter solution 👀
Shorter solution
Currently we know that the main conditional type is {} extends Pick<T, K> ? K : never
Also we know there is Key Remapping via as in TypeScript 4.1 💡
1type MappedTypeWithNewKeys<T> = {2 [K in keyof T as NewKeyType]: T[K];3 // ^^^^^^^^^^^^^4 // This is the new syntax!5};
We can try it in our shorter solution, it will look like that:
1type GetOptional<T> = {2 [K in keyof T as {} extends Pick<T, K> ? K : never]: T[K];3};
Let's sum up it again:
- We figured out how to identify optional keys and came up with the conditional type –
{} extends Pick<T, K> ? K : never
- We added
Values
first and used it to infer optional keys - Then we found the way to do it with key remapping via operator
as
To be able to see the final solution with all the test cases, please have a look at the Playground – https://tsplay.dev/m05JGW
Thank you for your time! 🕛
Have a wonderful evening 🌆 and see you soon! 👋
typescript