Issue
Let’s say I have an object like this:
const person = {
id: 87
name: 'Some Name',
address: {
street: 'Some street',
country: 'Some country'
}
}
I want to get a type that is the union of all key value pairs. So the type should be:
{ id: number } | { name: string } | { address: { street: string; country: string; } }
How can that be done? I tried this:
type PersonInfo = {
id: number;
name: string;
address: {
street: string;
country: string;
}
}
type PersonMap<M extends { [index: string]: any }> = {
[Key in keyof M]: M[Key]
};
type PersonTest1 = PersonMap<PersonInfo>[keyof PersonMap<PersonInfo>];
// Returns "number | string | { street: string; country: string; }"
type PersonTest2 = PersonMap<PersonInfo>;
// Returns { id: number; name: string; address: { street: string; country: string;} }
How can I get the union type described above?
Solution
It seems like you want a type function of the form UnionOfSingleKeyObjects<T>
which turns an object type T
into a union of single-key object types where each key in keyof T
appears exactly once. Depending on your use cases, you could define such a type function like this:
type UnionOfSingleKeyObjects<T extends object> =
{ [K in keyof T]-?: { [P in K]: T[P] } }[keyof T]
And verify that it works for PersonInfo
as desired:
type PersonKVPairs = UnionOfSingleKeyObjects<PersonInfo>
/* type PersonKVPairs = {
id: number;
} | {
name: string;
} | {
address: {
street: string;
country: string;
};
} */
The definition of UnionOfSingleKeyObjects
is a mapped type where we iterate over each key type K
in keyof T
, calculate the single-key object in question for each key, and then index into it with keyof T
to get a union of all single-key object types.
Instead of indexing into a mapped type, you could use distributive conditional types to get the same effect:
type UnionOfSingleKeyObjects<T extends object> =
keyof T extends infer K ? K extends keyof T ?
{ [P in K]: T[P] } : never : never
Either way works; I tend to use mapped types because they are a bit easier to explain than conditional type distributivity.
In both of those approaches, the single-key object with key K
is written as {[P in K]: T[P] }
. This can alternatively be written as Record<K, T[K]>
using the Record<K, V>
utility type, or as Pick<T, K>
using the Pick<T, K>
utility type. These other versions have their pros and cons, and may change how the type appears in IntelliSense quickinfo as well as whether or not optional/readonly
keys stay optional/readonly
in the output. If you care about preserving these modifiers and don’t want to see Pick
or Record
in your quickinfo, you can write it like {[P in keyof Pick<T, K>]: T[P]}
, like this:
type UnionOfSingleKeyObjects<T extends object> =
{ [K in keyof T]-?: { [P in keyof Pick<T, K>]: T[P] } }[keyof T]
and we can see that such modifiers are preserved:
type Example = UnionOfSingleKeyObjects<{ a?: string, readonly b: number }>
/* type Example = {
a?: string;
} | {
readonly b: number;
} */
Again, depending on your use cases, you may or may not care about such things.
Answered By – jcalz
This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0