Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Destructure/access a property that may or may not exist on an object union type

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?

like image 769
Michal Kurz Avatar asked Jan 26 '26 00:01

Michal Kurz


2 Answers

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 

TS Playground link: https://www.typescriptlang.org/play?#code/C4TwDgpgBAKuEEYoF4oG8qkgLigIgTwG4oAzAe3NwGdgAnASwDsBzAGigCMBDOgflxMIANwh0oAXwCwAKCzQ4kAEwp0meLjxLiZSgKhDRdDjzqCArgFtOYyVHtRZ8qAFUmDck1WLEUAD6w8EqysgDGnrRQACbkAMrklhDAABbMLKoAFOScAFYQocC4bh5MAJQoAHzosg5Q4UyRGPIcFOQmvHao2XkFNQ711OQANhAAdEPkLBnNum1cvKWy0jKyMfGJKWkZTRr4hC2Umq14kosyawlJqazb6jj42u1mUCoSZ0A

like image 130
riywo Avatar answered Jan 28 '26 14:01

riywo


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
}
like image 35
Lesiak Avatar answered Jan 28 '26 14:01

Lesiak