Given the following types:
interface FullName {
fullName?: string
}
interface Name {
firstName: string
lastName: string
}
type Person = FullName | Name;
const p1: Person = {};
const p2: Person = { fullName: 'test' };
const p3: Person = { firstName: 'test' }; // Does not throw
const p4: Person = { badProp: true }; // Does throw, as badProp is not on FullName | Name;
I would expect p3
to result in a compiler error, as firstName
is present without lastName
, but it doesn't -- is this a bug or expected?
Additionally, making FullName.fullName required results in p3
(and p1
) causing errors.
Discriminated union is a data structure used to hold a value that could take on different, fixed types. These are basically union types with a tag. To convert a union type into a discriminated union type, we use a common property across our types.
Creating Tagged Unions in TypeScript Introduction to Tagged Unions A tagged union is a data structure that holds several different data types, each of them distinguishable from one another using a discriminating property (usually called a “tag”).
The "property does not exist on type union" error occurs when we try to access a property that is not present on every object in the union type. To solve the error, use a type guard to ensure the property exists on the object before accessing it.
Intersection types are closely related to union types, but they are used very differently. An intersection type combines multiple types into one. This allows you to add together existing types to get a single type that has all the features you need.
First of, your interface FullName
does only contain one optional property, that is basically making it match anything. Then when you do a union type with it, the resulting type is going to be compatible with everything.
However, there is another concern considering declaring and assigning literal objects, and that is that you only can declare known properties: Why am I getting an error "Object literal may only specify known properties"?
So you could do this without any problem:
var test = { otherStuff: 23 };
const p4: Person = test;
But not this
const p4: Person = { otherStuff: 23 };
And in your case firstName
is a known property of FullName | Name
, so it's all ok.
And as @artem answered, discriminated unions
have a special meaning in typescript, apart from regular unions, requiring special structural assumptions.
The type in your question is not, in usual sense, discriminated union - your union members don't have common, non-optional literal property called discriminant.
So, as @Alex noted in his answer, your union is somewhat similar to
type Person = {
fullName?: string
firstName?: string
lastName?: string
}
so it can be initialized with { firstName: 'test' }
With true discriminated unions, you get back the logic for checking non-optional properties, as well as checking that object literal may only specify known properties:
interface FullName {
kind: 'fullname';
fullName?: string
}
interface Name {
kind: 'name';
firstName: string
lastName: string
}
type Person = FullName | Name;
const p1: Person = {kind: 'fullname'}; // ok
const p2: Person = {kind: 'fullname', fullName: 'test' }; // ok
checking non-optional property:
const p3: Person = {kind: 'name', firstName: 'test' };
error:
Type '{ kind: "name"; firstName: string; }' is not assignable to type 'Person'.
Type '{ kind: "name"; firstName: string; }' is not assignable to type 'Name'.
Property 'lastName' is missing in type '{ kind: "name"; firstName: string; }'.
checking extra property:
const p5: Person = { kind: 'fullname', bar: 42 }
error:
Type '{ kind: "fullname"; bar: number; }' is not assignable to type 'Person'.
Object literal may only specify known properties, and 'bar' does not exist in type 'Person'.
However, as @JeffMercado found out, type checking is still a bit off:
const p6: Person = { kind: 'fullname', firstName: 42 }; // no error. why?
I'd consider posting an issue for typescript github project.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With