'How to create a (python-like) NewType in typescript

In Python (typing) one can make a type alias (which is nothing more than a new name for a type) and a NewType, which is a type that is 100% the same as the underlying, but will be treated as unique by the type system. To quickly show the difference:

UserId = int   # Alias, meaning it's not considered a new type
def get_user(uid: UserId):
  # so this is the same as "def get_user(uid: int):"
  ...


get_user(3)  # and this is valid
UserId = typing.NewType("UserId", int)   # NewType, meaning it is considered a new type
def get_user(uid: UserId):
  # so this is NOT the same as "def get_user(uid: int):"
  ...


get_user(3)  # and this throws an error (in type checking)
get_user(UserId(3))  # (this works)
user_id = UserId(3)
get_user(user_id)  # as does this

Note that also in the case of NewType, at runtime the uid parameter is just an integer, and can be used in exactly that way.

In this trivial example the advantages of NewType are not so clear, but if you have a function with 5 parameters, some user_ids, some user_age, some department_id, etc, it's a life-saver if the type checker can alert you if you enter the parameters in the wrong order (and even better, if the IDE will just suggest the exact variable you need for each parameter, because it knows you have only one variable of type DepartmentId available!)

How to do the same in TypeScipt?

In TypeScript you can easily create type aliases:

type UserId = number

however, how to make something similar to NewType (where the typescript compiler will complain if you assign a simple number to a function that expects a UserId).

(Note that for custom objects, you could make a subclass; this question is mostly aimed at primitive objects (string, number, etc))



Solution 1:[1]

With a bit of typescript-hacking, you can make your own new types:

Define your new type as base type plus a fake field:

type UserId = number & {__my_hidden_type: "userId"}
type DepartmentId = number & {__my_hidden_type: "departmentId"}
// etc...

function getUser(uid: UserId) { }

getUserId(3)  // gives error
const notUserId = 3
getUserId(notUserId)  // gives error

getUserId(3 as UserId) // works

const userId = 3 as UserId
getUserId(userId) // works

For all intents and purposes, this does exactly what I would hope, except that the IDE thinks that there is a __my_hidden_type field on the uid variable; still, considering it's a hack, I would still be happy with a cleaner solution.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Claude