Search code examples
typescriptunion-types

Get value from mutually exclusive properties


Is there a cleaner way to get the value of either property (mutually exclusive) from a union? I can't add a discriminator/kind property because the object type comes from a package I don't maintain, and there's also no class type on either Object. I'd prefer a solution that doesn't require editing the library, casting types (which I currently have to do with as), or allowing undefined to be returned.

interface A {
  a: true
}
interface B {
  b: true
}

function getValue(obj: A | B): boolean | undefined {
  let value: boolean | undefined

  if (obj.hasOwnProperty("a")) {
    value = (obj as A).a
  } else if (obj.hasOwnProperty("b")) {
    value = (obj as B).b
  }

  return value
}

Solution

  • You can define custom type guard and use it to narrow the the type:

    const isA = (obj: A | B): obj is A => obj.hasOwnProperty("a");
    
    function getValue(obj: A | B): boolean | undefined {
        let value: boolean | undefined
    
        if (isA(obj)) {
            value = obj.a
        } else {
            value = obj.b
        }
    
        return value
    }
    
    // Or shorter
    const getValue = (obj: A | B) => isA(obj) ? obj.a : obj.b;
    

    Playground


    Another option is using in operator (acts as a narrowing expression for types):

    function getValue(obj: A | B): boolean | undefined {
        let value: boolean | undefined
    
        if ('a' in obj) {
            value = obj.a
        } else {
            value = obj.b
        }
    
        return value
    }
    

    Playground