Reputation: 2095
I get the following errors:
type Union = { type: "1"; foo: string } | { type: "2"; bar: number };
function doSomething = (object: Union) => {
const { foo } = object
// ^ TS2339: Property 'foo' does not exist on type 'Union'.
console.log(object.bar)
// ^ TS2339: Property 'bar' does not exist on type 'Union'.
}
Desired outcome:
typeof foo === string | undefined
typeof bar === number | undefined
How can I access the properties without explicitly type-guarding, for example:
const foo = o.type === 1 ? o.foo : undefined
const bar = o.type === 2 ? o.bar : undefined
this is not really an option for me, beacuse I'm working with large unions, where target properties may or may not be present on many objects, it would be a complete mess.
What other options do I have?
Upvotes: 14
Views: 6794
Reputation: 47
I did this:
type RequiredKeys<T> = {
[K in keyof T as T[K] extends undefined ? never : K]: T[K];
};
type OptionalKeys<T> = {
[K in keyof T as T[K] extends undefined ? K : never]: T[K];
};
type Union<T1 extends object, T2 extends object> = RequiredKeys<T1 | T2> &
OptionalKeys<T1 | T2> & {
[K in Exclude<keyof T1, keyof T2>]?: T1[K];
} & {
[K in Exclude<keyof T2, keyof T1>]?: T2[K];
};
Upvotes: 0
Reputation: 1518
You can set never
for unused properties, then TS can understand the type of these properties as optional.
type Type1 = { type: "1"; foo: string, bar?: never }
type Type2 = { type: "2"; foo?: never, bar: number }
type Union = Type1 | Type2
const doSomething = (object: Union) => {
const { type, foo, bar } = object
console.log(type, foo, bar)
}
doSomething({ type: "1", foo: "foo" }) // [LOG]: "1", "foo", undefined
doSomething({ type: "2", bar: 2 }) // [LOG]: "2", undefined, 2
Upvotes: 12
Reputation: 1133
The most convenient way I've found is to cast the variables based on their type.
type Type1 = { type: "1"; foo: string }
type Type2 = { type: "2"; bar: number }
type Union = Type1 | Type2
function doSomething = (object: Union) => {
const { foo } = object as Type1
const { bar } = object as Type2
const { type } = object
}
Upvotes: 3
Reputation: 26064
Check comment in Accessing property in union of object types fails for properties not defined on all union members #12815
The issue here is that because B doesn't declare an a property, it might at run-time have an a property of any possible type (because you can assign an object with any set of properties to a B as long as it has a b property of type string). You can make it work explicitly declaring an a: undefined property in B (thus ensuring that B won't have some random a property):
type A = { a: string; } type B = { b: string; a: undefined } type AorB = A | B; declare const AorB: AorB; if (AorB.a) { // Ok }
Upvotes: 3
Reputation: 107
This behaviour kinda makes sense because TS doesn't know which object from the Union it's dealing with and the property doesn't exist in some cases.
I'm not sure if that's what you are looking for, but you could try something like
type Union = { type: "1"; foo: string } | { type: "2"; bar: number };
function doSomething = (object: Union) => {
if ('foo' in object) {
const { foo } = object
}
if ('bar' in object) {
console.log(object.bar)
}
}
Upvotes: 1