Nominal Typing
Nominal Typing
The TypeScript type system is structural and this is one of the main motivating benefits. However, there are real-world use cases for a system where you want two variables to be differentiated because they have a different type name even if they have the same structure. A very common use case is identity structures (which are generally just strings with semantics associated with their name in languages like C#/Java).
There are a few patterns that have emerged in the community. I cover them in decreasing order of personal preference:
Using literal types
This pattern uses generics and literal types:
Advantages
No need for any type assertions
Disadvantage
The structure
{type,value}
might not be desireable and need server serialization support
Using Enums
Enums in TypeScript offer a certain level of nominal typing. Two enum types aren't equal if they differ by name. We can use this fact to provide nominal typing for types that are otherwise structurally compatible.
The workaround involves:
Creating a brand enum.
Creating the type as an intersection (
&
) of the brand enum + the actual structure.
This is demonstrated below where the structure of the types is just a string:
Using Interfaces
Because numbers
are type compatible with enum
s the previous technique cannot be used for them. Instead we can use interfaces to break the structural compatibility. This method is still used by the TypeScript compiler team, so worth mentioning. Using _
prefix and a Brand
suffix is a convention I strongly recommend (and the one followed by the TypeScript team).
The workaround involves the following:
adding an unused property on a type to break structural compatibility.
using a type assertion when needing to new up or cast down.
This is demonstrated below:
Last updated