Java’s type system is nominal, i.e.

class A {
  String x;
}
 
class B {
  String x;
}

are different types, we can’t assign a B to an A variable.

TypeScript’s type system is structural, i.e.

class A {
  x: string;
}
 
class B {
  x: string;
}

are equal types, that is let a: A = new B(); is allowed.

Anything that has a string property named x qualifies as an A.

Comparison

Nominal types are safer as they prevent accidental type equivalence. Structural types are more flexible, e.g. new new super-types can be created without adapting the existing subtypes.

Mimicking nominal types in TypeScript

Add a literal field to distinguish strucurally equal types:

type ValidatedString = string & { __brand: "validated" };
 
const print = (s: ValidatedString) => {
  console.log(s);
};
 
print('Plain string')  // compile error
print('Plain string' as ValidatedString)

We tell TypeScript, which nominal variant the object is by casting, i.e. we need to be careful at the “entrypoints” into nominal types.

References:

  • GitHub issue discussing nominal types for TypeScript (~500 comments)
  • Mimicking nominal types in TypeScript using brands
  • Blog post by Michal Zalecki comparing different approaches to nominal types in TypeScript