Document Workflow

Generate TypeScript types from an API JSON response

Create and review TypeScript interfaces from an API JSON sample while checking optional fields, mixed arrays, nulls, numeric precision, unusual keys, and runtime validation.

Written and tested by Published: Reviewed:

How this workflow was checked

For “Merge two user records into one response shape”, we entered the documented fixture in JSON to TypeScript Interface & Type Generator and followed “Validate the source as strict JSON” before “Generate from several representative records”. We compared the browser result with the stated output, then reviewed “Using production secrets as a convenient sample” and “Inferring optional fields from one object” as separate failure boundaries.

The two sample records merged into one users item interface with required id and optional name and active fields, rather than treating the first object as the entire schema.

Problem

A copied API response can save time when drafting TypeScript declarations, but sample-driven inference has blind spots. One record cannot prove whether a field is optional, an empty array cannot reveal its element type, null may appear only in later responses, and a long numeric identifier may already exceed JavaScript's exact integer range. Generated declarations are therefore a reviewable draft, not an authoritative schema or runtime validator.

When to use this

  • A new REST endpoint returns JSON and the client project needs an initial response type.
  • A webhook or third-party SDK example must be described before integration work begins.
  • Several fixture records should be merged to reveal missing or conflicting fields.
  • A bug report includes an unfamiliar payload that needs a readable structural summary.

Steps

  1. Step 1

    Redact and reduce the response

    Copy only the smallest representative payload that preserves the shape you need. Remove access tokens, cookies, email addresses, account identifiers, and unrelated records. Keep at least two array items when they contain different fields, because one item cannot demonstrate optionality.

  2. Step 2

    Validate the source as strict JSON

    Run the sample through JSON Validator or JSON Formatter before inference. Fix comments, trailing commas, malformed escapes, multiple root values, and duplicate keys explicitly. A duplicate key is ambiguous because different parsers may keep different values.

  3. Step 3

    Choose a stable root name and declaration style

    Use a PascalCase name that matches the domain, such as ApiResponse, OrderList, or WebhookEvent. Choose interface when object extension and declaration merging fit the codebase, or type when aliases are the established convention. Array and scalar roots still require a type alias.

  4. Step 4

    Generate from several representative records

    Keep optional inference enabled when objects in the same array intentionally demonstrate missing fields. The generator merges those object samples, quotes property names that are not TypeScript identifiers, creates nested declarations, and uses unions when values have different JSON kinds.

  5. Step 5

    Review every inference warning

    Replace Array<unknown> only after learning the real element type. Review empty object declarations, heterogeneous array unions, null members, and numeric precision warnings. Date strings remain string, every JSON number becomes number, and a field absent outside the sampled array cannot be discovered.

  6. Step 6

    Compile and validate at the boundary

    Add the generated file to the target project and run its TypeScript compiler and lint rules. Compare the declarations with API documentation or an OpenAPI, JSON Schema, or GraphQL contract. Then validate untrusted responses at runtime before narrowing them to the generated type.

Example

Merge two user records into one response shape

Input

{
  "users": [
    { "id": "usr_1", "name": "Ada" },
    { "id": "usr_2", "active": true }
  ]
}

Output

export interface ApiResponse {
  users: Array<ApiResponseUsersItem>;
}

export interface ApiResponseUsersItem {
  id: string;
  name?: string;
  active?: boolean;
}

Common mistakes

Using production secrets as a convenient sample

Local processing reduces network exposure, but secrets and personal data still do not belong in an unnecessary working copy. Redact first and keep only shape-relevant values.

Inferring optional fields from one object

A single object only shows fields that happened to be present. Include several representative items or consult the authoritative schema before adding or removing optional markers.

Replacing unknown with a guess

An empty array provides no evidence for its element type. A guessed string[] or object[] can hide a contract error until a real response arrives.

Ignoring exact numeric identifiers

TypeScript number uses JavaScript number semantics. Long account IDs, order numbers, and exact decimals should often arrive as strings instead of numbers.

Skipping runtime validation

Static types disappear at runtime. A server can still return missing, extra, or differently typed values, so validate external input before the rest of the application trusts it.

FAQ

Can one API response produce a complete TypeScript contract?

No. It can produce a useful draft, but only documentation, a schema, or a representative set of responses can establish optional fields and all possible variants.

Why does an empty array become Array<unknown>?

There is no element value to infer. Unknown records that uncertainty without allowing unsafe property access, and you can replace it after checking the real contract.

Should nullable fields also be optional?

Not automatically. Null means the field is present with no value, while optional means it may be absent. The correct combination depends on the API contract.

Why are some property names quoted?

JSON permits keys that TypeScript cannot use as bare identifiers, including spaces, hyphens, numeric starts, and line breaks. Quoting preserves the exact key and valid syntax.

Do I still need Zod, JSON Schema, or another validator?

Use runtime validation whenever untrusted data crosses into the application. Generated TypeScript declarations improve editor and compiler checks but cannot inspect a live response.