When we make a union of objects in typescript that do not have a discriminator, it provides neither convenience nor type safety:
type X = { a: string, b: string } | { c: string; d: string }
const x = { c: 'qwe', d: 'asd', a: undefined } as X // notice a: undefined
if (x.a) {} // Error: "Property 'a' does not exist on type 'X'."
Instead, it's recommended to use the "in" operator:
if ('a' in x) {
x.a // string, wrong
x.b // string, wrong
}
The above is wrong and not safe. You should do:
if ('a' in x && x.a != null) {} // but this is NOT enforced by typescript
A combined set of fields is something we can work with:
type X1 =
| { a: string, b: string, c?: undefined, d?: undefined }
| { c: string; d: string, a?: undefined, b?: undefined }
const x1 = { c: 'qwe', d: 'asd', a: undefined } as X1
if ('a' in x1) {
x1.a // string | undefined, correct
}
if (x1.a) {
x1.a // string, correct
x1.b // string, correct
}
A typescript helper to achieve this may be useful:
type Result = UnionHelper<{ a: string, b: string } | { c: string, d: string }>
/**
* Now Result is
* | { a: string, b: string, c?: undefined, d?: undefined }
* | { c: string; d: string, a?: undefined, b?: undefined }
*/