Type ISO Date Strings vs Plain Strings in JSON-to-TypeScript

Use json to typescript with a strategy for date fields — keep them as ISO strings at the type boundary and parse to Date only at the UI edge.

TypeScript Patterns

Detailed Explanation

Why "Date" Is the Wrong Type for JSON

JSON.parse() never returns Date instances — it returns strings. Marking a field as Date in your generated type creates a lie that compiles but fails at runtime.

Example JSON

{
  "id": "evt_1",
  "title": "Conference talk",
  "starts_at": "2024-09-12T14:00:00Z",
  "ends_at": "2024-09-12T15:30:00Z",
  "created_at": "2024-03-01T08:00:00Z"
}

Three Strategies

Strategy 1 — plain string (default, safest):

interface Event {
  id: string;
  title: string;
  starts_at: string;
  ends_at: string;
  created_at: string;
}

Strategy 2 — branded ISO string (best at scale):

type ISODateString = string & { readonly __brand: "ISODateString" };

interface Event {
  id: string;
  title: string;
  starts_at: ISODateString;
  ends_at: ISODateString;
  created_at: ISODateString;
}

function asISODate(s: string): ISODateString {
  if (!/^\d{4}-\d{2}-\d{2}T/.test(s)) throw new Error("Not ISO 8601");
  return s as ISODateString;
}

The brand prevents accidentally passing a free-text string into a function expecting a date string, while keeping the runtime type identical to a JSON string.

Strategy 3 — Date instances (only after a parse step):

interface ParsedEvent {
  id: string;
  title: string;
  starts_at: Date;
  ends_at: Date;
  created_at: Date;
}

function parseEvent(raw: Event): ParsedEvent {
  return { ...raw, starts_at: new Date(raw.starts_at), ends_at: new Date(raw.ends_at), created_at: new Date(raw.created_at) };
}

Use this only for the type that exists after explicit parsing. Mixing raw JSON shapes with Date types in the same interface guarantees runtime crashes.

Recommendation

Generate with Strategy 1, harden critical fields with Strategy 2, and reach for Strategy 3 only inside dedicated parse layers. The boundary is the JSON.parse call — before it, types are strings; after it (with explicit conversion), types may be Dates.

Use Case

Building a calendar UI that consumes event JSON from a Rails API and needs to compute durations, format times in the user's locale, and never accidentally treat a parsed Date as still being JSON.

Try It — JSON to TypeScript

Open full tool