NV ToolkitFormat · Validate · Convert

Code Generators

JSON to TypeScript Converter

Paste a JSON or XML sample — the format is auto-detected — and get typed TypeScript models with names, nesting and nullability inferred for you.

In short

JSON to TypeScript infers interfaces from a sample document: every nested object becomes an interface, arrays become Item[] after unifying element shapes, and a field seen only as null is typed null. The generator is quicktype, running in your browser; the output includes a Convert class that validates a payload against the generated types, and XML input is accepted too.

By NaveenKumar T · Updated

Learn: JSON Examples: From Hello World to Real API Payloads

  • JSON or XML input, auto-detected
  • Nested objects become types
  • Nullability inferred

Runs entirely in your browser — nothing you paste or open here is uploaded, logged, or stored.How we handle data →

Example

Example: JSON order payload to TypeScript

A small order document with a nested object, an array of two items, a decimal total and a null field — enough to show how each TypeScript type, list and nullable is inferred. The same page accepts the XML form of this order too, so the sample below is an example rather than the only accepted format. The right-hand side is the exact output of this tool.

Input · JSON
{
  "id": "ord_9F2KX",
  "status": "processing",
  "paid": true,
  "customer": { "name": "Ada Lovelace", "email": "ada@example.com", "phone": null },
  "items": [
    { "sku": "KB-75", "qty": 1, "price": 149.99 },
    { "sku": "MS-12", "qty": 2, "price": 39.5 }
  ],
  "total": 228.99
}
Generated · TypeScript
// To parse this data:
//
//   import { Convert, Root } from "./file";
//
//   const root = Convert.toRoot(json);
//
// These functions will throw an error if the JSON doesn't
// match the expected interface, even if the JSON is valid.

export interface Root {
    id:       string;
    status:   string;
    paid:     boolean;
    customer: Customer;
    items:    Item[];
    total:    number;
}

export interface Customer {
    name:  string;
    email: string;
    phone: null;
}

export interface Item {
    sku:   string;
    qty:   number;
    price: number;
}

// Converts JSON strings to/from your types
// and asserts the results of JSON.parse at runtime
export class Convert {
    public static toRoot(json: string): Root {
        return cast(JSON.parse(json), r("Root"));
    }

    public static rootToJson(value: Root): string {
        return JSON.stringify(uncast(value, r("Root")), null, 2);
    }
}

function invalidValue(typ: any, val: any, key: any, parent: any = ''): never {
    const prettyTyp = prettyTypeName(typ);
    const parentText = parent ? ` on ${parent}` : '';
    const keyText = key ? ` for key "${key}"` : '';
    throw Error(`Invalid value${keyText}${parentText}. Expected ${prettyTyp} but got ${JSON.stringify(val)}`);
}

function prettyTypeName(typ: any): string {
    if (Array.isArray(typ)) {
        if (typ.length === 2 && typ[0] === undefined) {
            return `an optional ${prettyTypeName(typ[1])}`;
        } else {
            return `one of [${typ.map(a => { return prettyTypeName(a); }).join(", ")}]`;
        }
    } else if (typeof typ === "object" && typ.literal !== undefined) {
        return typ.literal;
    } else {
        return typeof typ;
    }
}

function jsonToJSProps(typ: any): any {
    if (typ.jsonToJS === undefined) {
        const map: any = {};
        typ.props.forEach((p: any) => map[p.json] = { key: p.js, typ: p.typ });
        typ.jsonToJS = map;
    }
    return typ.jsonToJS;
}

function jsToJSONProps(typ: any): any {
    if (typ.jsToJSON === undefined) {
        const map: any = {};
        typ.props.forEach((p: any) => map[p.js] = { key: p.json, typ: p.typ });
        typ.jsToJSON = map;
    }
    return typ.jsToJSON;
}

function transform(val: any, typ: any, getProps: any, key: any = '', parent: any = ''): any {
    function transformPrimitive(typ: string, val: any): any {
        if (typeof typ === typeof val) return val;
        return invalidValue(typ, val, key, parent);
    }

    function transformUnion(typs: any[], val: any): any {
        // val must validate against one typ in typs
        const l = typs.length;
        for (let i = 0; i < l; i++) {
            const typ = typs[i];
            try {
                return transform(val, typ, getProps);
            } catch (_) {}
        }
        return invalidValue(typs, val, key, parent);
    }

    function transformEnum(cases: string[], val: any): any {
        if (cases.indexOf(val) !== -1) return val;
        return invalidValue(cases.map(a => { return l(a); }), val, key, parent);
    }

    function transformArray(typ: any, val: any): any {
        // val must be an array with no invalid elements
        if (!Array.isArray(val)) return invalidValue(l("array"), val, key, parent);
        return val.map(el => transform(el, typ, getProps));
    }

    function transformDate(val: any): any {
        if (val === null) {
            return null;
        }
        const d = new Date(val);
        if (isNaN(d.valueOf())) {
            return invalidValue(l("Date"), val, key, parent);
        }
        return d;
    }

    function transformObject(props: { [k: string]: any }, additional: any, val: any): any {
        if (val === null || typeof val !== "object" || Array.isArray(val)) {
            return invalidValue(l(ref || "object"), val, key, parent);
        }
        const result: any = {};
        Object.getOwnPropertyNames(props).forEach(key => {
            const prop = props[key];
            const v = Object.prototype.hasOwnProperty.call(val, key) ? val[key] : undefined;
            result[prop.key] = transform(v, prop.typ, getProps, key, ref);
        });
        Object.getOwnPropertyNames(val).forEach(key => {
            if (!Object.prototype.hasOwnProperty.call(props, key)) {
                result[key] = transform(val[key], additional, getProps, key, ref);
            }
        });
        return result;
    }

    if (typ === "any") return val;
    if (typ === null) {
        if (val === null) return val;
        return invalidValue(typ, val, key, parent);
    }
    if (typ === false) return invalidValue(typ, val, key, parent);
    let ref: any = undefined;
    while (typeof typ === "object" && typ.ref !== undefined) {
        ref = typ.ref;
        typ = typeMap[typ.ref];
    }
    if (Array.isArray(typ)) return transformEnum(typ, val);
    if (typeof typ === "object") {
        return typ.hasOwnProperty("unionMembers") ? transformUnion(typ.unionMembers, val)
            : typ.hasOwnProperty("arrayItems")    ? transformArray(typ.arrayItems, val)
            : typ.hasOwnProperty("props")         ? transformObject(getProps(typ), typ.additional, val)
            : invalidValue(typ, val, key, parent);
    }
    // Numbers can be parsed by Date but shouldn't be.
    if (typ === Date && typeof val !== "number") return transformDate(val);
    return transformPrimitive(typ, val);
}

function cast<T>(val: any, typ: any): T {
    return transform(val, typ, jsonToJSProps);
}

function uncast<T>(val: T, typ: any): any {
    return transform(val, typ, jsToJSONProps);
}

function l(typ: any) {
    return { literal: typ };
}

function a(typ: any) {
    return { arrayItems: typ };
}

function u(...typs: any[]) {
    return { unionMembers: typs };
}

function o(props: any[], additional: any) {
    return { props, additional };
}

function m(additional: any) {
    return { props: [], additional };
}

function r(name: string) {
    return { ref: name };
}

const typeMap: any = {
    "Root": o([
        { json: "id", js: "id", typ: "" },
        { json: "status", js: "status", typ: "" },
        { json: "paid", js: "paid", typ: true },
        { json: "customer", js: "customer", typ: r("Customer") },
        { json: "items", js: "items", typ: a(r("Item")) },
        { json: "total", js: "total", typ: 3.14 },
    ], false),
    "Customer": o([
        { json: "name", js: "name", typ: "" },
        { json: "email", js: "email", typ: "" },
        { json: "phone", js: "phone", typ: null },
    ], false),
    "Item": o([
        { json: "sku", js: "sku", typ: "" },
        { json: "qty", js: "qty", typ: 0 },
        { json: "price", js: "price", typ: 3.14 },
    ], false),
};

Look for the "phone" field: sampled as null, it is typed as null. "items" became Item[], and "total" is number.

Learn more

From JSON sample to TypeScript types

Hand-writing model classes from a JSON payload is transcription work: read a field, type a property, repeat eighty times, miss one. This page runs quicktype (the same engine behind the best-known generators) against your document, merging array elements to find the common shape and naming nested types from their keys.

number is a 64-bit float, so an integer ID above 2^53 rounds silently — and the runtime Convert cast will not catch it, because it checks the type, not the range. Keep 64-bit IDs as strings on the wire, or parse with a reviver that turns them into BigInt, and change the interface field to string or bigint by hand.

How JSON values become TypeScript types

Inference works from the values in your sample. Strings map to string, integers and decimals are told apart (228.99 becomes number, while 1 and 2 stay integral), true/false become the language's boolean, and an object such as "customer" becomes its own named type. The "items" array is typed as Item[] because every element shares the same keys — quicktype unifies the shapes of all array elements into one Item type.

Nulls are the one place inference needs your help: "phone": null carries no type information, so the field is emitted as null. If the real API sometimes sends a string there, include a sample where the field is populated (paste several documents merged into one array) and the generated type becomes the union of what it saw.

Pasting XML works here too — a sample starting with "<" is parsed before inference runs — but if XML is your usual input, XML to TypeScript is the page written for it: same generator, with the element-versus-attribute typing rules and a worked XML example.

Looking for a runtime validator rather than compile-time-only interfaces? JSON to Zod runs the same inference through Zod's schema-builder API instead, so the result both validates a payload at runtime AND still gives you the TypeScript type via z.infer — useful the moment this page's interfaces need to survive contact with a real, possibly-drifted API response.

Annotations, nulls and nested arrays in the generated TypeScript

TypeScript output carries no decorators. The interfaces keep your wire names exactly as they arrived, quoting them when they are not valid identifiers, so a key like customer-ref needs no mapping layer at all. Runtime safety lives in the Convert class and the typeMap beside it: every property becomes an entry pairing the JSON key with the JS key and a type token, with combinators for unions, arrays and references, and a cast function that walks the map on parse. A mismatch throws an error naming the key and its parent type.

A property whose only sampled value is null is typed null — literally the null type — and the runtime cast then enforces exactly that. Merge a document where the field carries a value and it widens into a proper union such as null | string, with a matching union token in the typeMap. A key that is missing from some samples becomes optional instead, marked with a question mark and an undefined branch, so TypeScript keeps absent and null apart. Empty arrays fall back to any[], the one place the generated types stop helping you.

Nested objects become their own exported interfaces named after the key — Customer, Item — referenced by name, so a function can import just the piece it needs. Arrays of objects are typed Item[] and validated element by element at runtime, not just at the first index. Give arrays at least two elements: quicktype merges their keys and marks anything missing from some of them optional, which is how you get honest question marks. A single element makes every field look required, and an empty array yields any[].

Help

Frequently asked questions

Everything you need to know about the JSON to TypeScript.

01How do I generate TypeScript classes from JSON?

Paste a representative document — JSON or XML, the format is detected automatically — and press Convert. A real API response works best. Nested objects become their own TypeScript types, arrays become Item[], and the result downloads as Root.ts.

02Can I get Flow types instead of TypeScript?

The Output switch above the editor toggles between TypeScript and Flow. TypeScript emits exported interfaces plus a Convert class; Flow emits the same shapes as exported object types under a // @flow pragma, ending in a CommonJS export of toRoot and rootToJson. The runtime typeMap and cast are the same machinery in both, so the only real differences are the pragma, the object-type syntax (inexact braces rather than interfaces) and the module ending — pick Flow if your React codebase is Flow-checked.

03What does the generated TypeScript code include?

TypeScript output gives you interfaces for the full payload shape plus a runtime Convert helper that validates while it parses — types for the compiler AND checks for runtime, from one sample.

04How do I use the generated TypeScript code in my project?

Save the file as a module and import { Convert, Root }. Convert.toRoot(json) parses a JSON string and validates it against the interfaces, throwing if the shape has drifted; Convert.rootToJson(root) serializes. If you only want compile-time types, the exported interfaces are enough on their own.

05Interfaces only, or runtime validation too?

Both: interfaces describe the shape for the compiler, and the included Convert class walks the data at runtime, throwing precise errors when an API response drifts from the generated contract — the part hand-written types silently miss.

Keep working

Related tools

Learn the format

Related guides

All guides →