I have a disciminated union
type MyDUnion = { type: "anon"; name: string } | { type: "google"; idToken: string };
I want to access the name key from the discriminative union, from the MyDUnion
type directly. Something like this
type Name = MyDUnion['name']
But typescript won't allow that
Property 'name' doesn't exist on type '{ type: "anon"; name: string } | { type: "google"; idToken: string }'
How can I access it?
To be clear, this is not a valid solution:
type MyName = string;
type MyDUnion = { type: "anon"; name: MyName } | { type: "google"; idToken: string };
type Name = MyName; // this line would be in a different file
This is not valid, because then I would have to export both MyName
and MyDUnion
types to be used elsewhere.
Any ideas?
I have a disciminated union
type MyDUnion = { type: "anon"; name: string } | { type: "google"; idToken: string };
I want to access the name key from the discriminative union, from the MyDUnion
type directly. Something like this
type Name = MyDUnion['name']
But typescript won't allow that
Property 'name' doesn't exist on type '{ type: "anon"; name: string } | { type: "google"; idToken: string }'
How can I access it?
To be clear, this is not a valid solution:
type MyName = string;
type MyDUnion = { type: "anon"; name: MyName } | { type: "google"; idToken: string };
type Name = MyName; // this line would be in a different file
This is not valid, because then I would have to export both MyName
and MyDUnion
types to be used elsewhere.
Any ideas?
Share Improve this question asked Jan 13, 2023 at 10:32 sayandcodesayandcode 3,2642 gold badges18 silver badges32 bronze badges3 Answers
Reset to default 7In order to filter union of objects, usually you need to use Extract: The easy way:
type Result = Extract<MyDUnion , {type: "anon"}>
The more robust:
type MyDUnion = { type: "anon"; name: string } | { type: "google"; idToken: string };
type Filter<Union, Type extends Partial<Union>> = Extract<Union, Type>
type Result = Filter<MyDUnion, { type: 'anon' }>
Generic Solution
/**
* @param Union - A discriminated union type to extract properties from.
* @param Keys - The specific properties to extract from `Union`.
* @defaultValue all `KeyOf<Union>`
* @param Otherwise - The type to unionize with value types that don't exist in all members of `Union`.
* @defaultValue `undefined`
*/
export type PickAll<
Union,
Keys extends KeyOf<Union> = KeyOf<Union>,
Otherwise = undefined
> = {
[_K in Keys]: Union extends { [K in _K]?: infer Value }
? UnionToIntersection<Value>
: Otherwise
}
helpers
type KeyOf<Union, Otherwise = never> = Union extends Union
? keyof Union
: Otherwise
type UnionToIntersection<U> = (
U extends any ? (k: U) => void : never
) extends (k: infer I) => void
? I
: never
Target
type MyDUnion =
| { type: 'anon'; name: string }
| { type: 'google'; idToken: string }
indexed access
and keyof
MyDUnion['type']
/* "anon' | "google" */
// OK
MyDUnion[keyof MyDUnion]
/* "anon" | "google" */
// ❓
// @ts-expect-error - not all union members have an idToken
MyDUnion['type' | 'idToken']
/* any */
// ❌
KeyOf<Union>
type UnionKeys = KeyOf<MyDUnion>
/* "type" | "name" | "idToken" */
// ✅
PickAll<Union, KeyOf<Union>?, Otherwise?>
by default, picks all
type DUnionProps = PickAll<MyDUnion>
/* {
type: "anon" | "google";
name: string | undefined;
idToken: string | undefined;
} */
// ✅
focus on a specific Key
(+IDE prompts and type checking)
ctrl
+space
is OP
type DUnionName = PickAll<MyDUnion, 'name'>
/* {
name: string | undefined
} */
// ✅
or a union of Keys
type DesiredProps = PickAll<
MyDUnion | { fish: number },
'type' | 'idToken' | 'fish'
>
/* {
type: "anon" | "google" | undefined;
idToken: string | undefined;
fish: number | undefined;
} // ✅ */
Gotchas
Does not distinguish between
undefined
andoptional
properties. It can be done though and its on the todo.Extract literals directly.
DON'T do this:
type
should be "anon"
| "google"
type GoogleLike = PickAll<MyDUnion, 'type' | 'name'>
type g = GoogleLike['name' | 'type']
/* string | undefined