Generate Branded Types from JSON IDs in TypeScript

Use json to typescript with branded types so UserId and OrderId never get accidentally swapped, even though both are strings at runtime.

TypeScript Patterns

Detailed Explanation

Nominal Typing for ID Fields

JSON IDs are usually strings, but a UserId and an OrderId should never be interchangeable. Branded types add a phantom marker to the type without touching the runtime value.

Example JSON

{
  "user_id": "usr_01H8X9",
  "order_id": "ord_42",
  "product_id": "prd_AB12",
  "quantity": 3
}

Generated TypeScript

type Brand<T, B> = T & { readonly __brand: B };

type UserId = Brand<string, "UserId">;
type OrderId = Brand<string, "OrderId">;
type ProductId = Brand<string, "ProductId">;

interface OrderLine {
  user_id: UserId;
  order_id: OrderId;
  product_id: ProductId;
  quantity: number;
}

Why a Phantom Brand?

The intersection with { __brand: "UserId" } exists only at compile time. At runtime, a UserId is just a string, so JSON serialization, comparison, and storage all work unchanged. But the compiler refuses to assign a string (or an OrderId) to a UserId parameter:

function loadUser(id: UserId): Promise<User> { /* ... */ }

const o: OrderId = parseOrderId(req.body.order_id);
loadUser(o);                  // Error: OrderId not assignable to UserId
loadUser(req.body.user_id);   // Error: string not assignable to UserId

Constructing Branded Values

You need a small parser per brand:

function asUserId(s: string): UserId {
  if (!s.startsWith("usr_")) throw new Error("Invalid UserId");
  return s as UserId;
}

This becomes the only legitimate way to mint a UserId, so you can audit ID provenance with a single grep.

When to Apply

Apply brands to every ID field that flows across module boundaries: API responses, database rows, function parameters. Skip them on purely local strings (component state, formatting). The result is a type system that catches the entire class of "passed user.id where order.id was expected" bugs at compile time.

Use Case

Stopping a recurring class of production bugs where developers passed an organization ID to a function expecting a user ID, both being strings at runtime.

Try It — JSON to TypeScript

Open full tool