Blog
Wild & Free Tools

Converting Nested JSON to TypeScript — Objects, Arrays, and Deep Trees

Last updated: February 2026 7 min read
Quick Answer

Table of Contents

  1. Nested Objects Become Named Interfaces
  2. Arrays of Objects
  3. Deeply Nested Trees
  4. Mixed Array Element Types
  5. Nested Optional and Null Chains
  6. Frequently Asked Questions

Flat JSON is easy. A single object with primitive fields maps to a single interface in one step. The real shape of API responses is almost never flat — you have objects inside objects, arrays of objects, objects with arrays of objects with more objects, and sometimes the nesting goes four or five levels deep.

Here's how a well-behaved generator handles each layer, with examples of every common shape you'll hit.

Nested Objects Become Their Own Named Interfaces

A nested object could be inlined as an anonymous type, but most codebases name each shape so it can be referenced elsewhere. The generator follows that convention.

Input:

{
  "id": 1,
  "author": {
    "name": "Jamie",
    "verified": true
  }
}

Output:

interface Author {
  name: string;
  verified: boolean;
}

interface Root {
  id: number;
  author: Author;
}

The Author interface becomes reusable — if three different endpoints return an author, you import Author from one file and reuse it everywhere. Inlined types can't do that.

Arrays of Objects

The common API shape is an array of records. The generator extracts the item shape into its own interface and types the array as Item[].

Input:

{
  "users": [
    { "id": 1, "name": "A" },
    { "id": 2, "name": "B" }
  ]
}

Output:

interface User {
  id: number;
  name: string;
}

interface Root {
  users: User[];
}

The item type is inferred from the first element. If subsequent elements have different shapes (additional optional fields, different types for the same field), you'll see drift at runtime. Paste a few different samples and merge the interfaces by hand, or switch to runtime validation so you catch drift at the boundary.

Sell Custom Apparel — We Handle Printing & Free Shipping

Deeply Nested Trees — 4, 5, 6 Levels Deep

Generators handle depth by recursion. There's no practical limit — a tree 10 levels deep generates cleanly, as long as the JSON is valid.

Input:

{
  "org": {
    "team": {
      "lead": {
        "profile": {
          "social": {
            "twitter": "@jamie"
          }
        }
      }
    }
  }
}

Output (one interface per level):

interface Social { twitter: string; }
interface Profile { social: Social; }
interface Lead { profile: Profile; }
interface Team { lead: Lead; }
interface Org { team: Team; }
interface Root { org: Org; }

Six interfaces, one per nesting level. In your code, data.org.team.lead.profile.social.twitter is fully typed and autocompletes at every step.

A practical note: deep nesting is usually a smell in API design — it hints that the shape should be normalized or flattened. If you're generating and consuming an interface this deep, it's worth questioning whether the API itself could be simpler.

Mixed Array Element Types

JSON allows arrays where every element is a different type. TypeScript handles it with union types:

Input:

{
  "values": [1, "two", true]
}

Output:

interface Root {
  values: (number | string | boolean)[];
}

Technically correct, but (number | string | boolean)[] is almost always worse than what the API really means. If the real API sends one of: an integer ID, a string name, or a boolean flag — each in a different element position — what you probably want is a tuple: [number, string, boolean]. That's a manual edit.

If the array represents a discriminated union (items with a type field), the generator will only see the first variant. For full coverage, paste samples that include every variant and combine them into a union type by hand.

Nested Optional and Null Chains

A nested field can be null or missing at multiple levels. TypeScript forces you to check at each level, which is painful without optional chaining:

interface Root {
  user?: {
    profile?: {
      avatar: string | null;
    };
  };
}

// Consumer code:
const avatar = data.user?.profile?.avatar ?? '/default.png';

Generated interfaces lean toward strict — if a field is present in your sample, it's typed as present. If a field is missing or null across multiple samples, mark it optional or nullable in the output.

For APIs where entire sub-trees might be missing depending on user state or permissions, it's cleaner to type those sub-trees as Profile | null than to mark every individual field optional. Saves a lot of ?. in consumer code.

Try It With Your Nested JSON

Paste any depth of nested JSON, get clean typed interfaces for every level.

Open Free JSON to TypeScript Generator

Frequently Asked Questions

Is there a depth limit for nested JSON?

No practical limit. The generator recurses through the entire tree and produces one interface per nested object.

What if my array has objects with different fields?

Only the first element is inspected for shape. For full coverage, merge the inferred interface with additional fields by hand or paste samples of each variant.

Why does the generator name nested interfaces from the field name?

It uses the parent key as the inferred name (e.g., author becomes Author). You can rename them after pasting — TypeScript does not care what the interface is called.

How are empty arrays typed?

Empty arrays become any[] since there is no element to infer from. Paste a non-empty sample for a specific type.

David Rosenberg
David Rosenberg Technical Writer

David spent ten years as a software developer before shifting to technical writing covering developer productivity tools.

More articles by David →
Launch Your Own Clothing Brand — No Inventory, No Risk