JSON to TypeScript and JSON Schema
Turn sample JSON into TypeScript interfaces and a draft 2020-12 JSON Schema, with optional keys and unions inferred from the samples.
A clear answer. Here’s how.
Walks every sample and records, for each place in the document, which JSON kinds occur there (object, array, string, number, boolean, null) and, for objects, each key and how many samples had it. A key present in every sample is required; one missing from some is optional (key?: in TypeScript, left out of required in the schema). Different kinds at one place become a union (string | null; a type list or anyOf in the schema), array elements are merged into one element type, and an empty array gives unknown[]. Names are deterministic: PascalCase from the key, a singular form for array elements (orders gives Order, entries Entry, people Person), and a numeric suffix when a name is taken. The TypeScript uses interfaces or type aliases, optionally readonly; the schema follows JSON Schema draft 2020-12 (type, properties, required, items) with integer when every number is whole, and format date-time (RFC 3339), email or uri only when every string at that place matches and the option is on. Dates stay string in TypeScript. Inference only sees the samples given: it cannot know about keys, values or constraints they do not show, and it does not generate enums, tuples, additionalProperties or $defs.
How this is checked
5 reference checks passed These 5 cases were run when this page was built, on 20 October 2026, and all reproduce within the stated tolerance. The same cases are part of the automated tests that run on every change to Toolmoz. Engine version 1.
See the checks and where each expected answer comes from
Case 1
- Inputs
- Sample JSON {"id":1,"name":"Ann"}
- Expected, and what Toolmoz gave
- text expected export interface Root { id: number; name: string; } , Toolmoz export interface Root { id: number; name: string; } Types declared expected 1, Toolmoz 1
- Where the expected answer comes from
- Hand derivation: one object with two keys present in the only sample, so both are required; 1 is a number and "Ann" a string
- Allowed difference
- exact
Case 2
- Inputs
- Sample JSON {"id":1,"tag":"a"} {"id":2}The input is lines
- Expected, and what Toolmoz gave
- text expected export interface Root { id: number; tag?: string; } , Toolmoz export interface Root { id: number; tag?: string; } Optional properties expected 1, Toolmoz 1
- Where the expected answer comes from
- Hand derivation: id appears in 2 of 2 samples (required); tag in 1 of 2 (optional, tag?)
- Allowed difference
- exact
Case 3
- Inputs
- Sample JSON {"items":[{"sku":"A","qty":1},{"sku":"B","qty":2.5,"note":null}]}
- Expected, and what Toolmoz gave
- text expected export interface Root { items: Item[]; } export interface Item { sku: string; qty: number; note?: null; } , Toolmoz export interface Root { items: Item[]; } export interface Item { sku: string; qty: number; note?: null; } Types declared expected 2, Toolmoz 2
- Where the expected answer comes from
- Hand derivation: items holds 2 objects, merged into one element type named Item (singular of items); sku and qty in 2 of 2, note in 1 of 2 and only ever null
- Allowed difference
- exact
Case 4
- Inputs
- Sample JSON [1,"a",null]
- Expected, and what Toolmoz gave
- text expected export type Root = (string | number | null)[]; , Toolmoz export type Root = (string | number | null)[]; Unions expected 1, Toolmoz 1
- Where the expected answer comes from
- Hand derivation: the root is an array whose elements are a number, a string and null, so the element type is one union, string | number | null
- Allowed difference
- exact
Case 5
- Inputs
- Sample JSON {"when":"2026-10-18T09:30:00Z","n":3}Show schema
- Expected, and what Toolmoz gave
- text expected { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Root", "type": "object", "properties": { "when": { "type": "string", "format": "date-time" }, "n": { "type": "integer" } }, "required": [ "when", "n" ] } , Toolmoz { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Root", "type": "object", "properties": { "when": { "type": "string", "format": "date-time" }, "n": { "type": "integer" } }, "required": [ "when", "n" ] }
- Where the expected answer comes from
- Hand derivation: when is an RFC 3339 date-time string in every sample, n is a whole number (integer), and both keys are present, so both are required
- Allowed difference
- exact
What this shows: the engine reproduces independently worked values for these cases. It does not prove every possible input is right. To test an answer you were given, see how to check a number; to see what is and is not checked across Toolmoz, see the accuracy ledger.
See a worked example
Worked example
A fixed demonstration, separate from the values you enter above.
- Sample JSON
{ "id": 42, "name": "Ann", "email": "ann@example.com", "joined": "2026-10-18T09:30:00Z", "tags": ["admin", "editor"], "address": { "city": "Leeds", "postcode": null }, "orders": [ { "sku": "A-1", "qty": 2 }, { "sku": "B-7", "qty": 1, "note": "gift" } ] }- The input is
- One JSON document
- Name of the root type
- Root
- TypeScript declarations
- export interface
- Readonly properties and arrays
- No
- String formats in the schema
- Add date-time, email or uri when every sample matches
- Show
- TypeScript
Example result
- Samples
- 1
- Values read
- 19
- Types declared
- 3
- Optional properties
- 1
- Unions
- 0
Inferred from 1 sample only: a key missing from some samples becomes optional (?), different kinds of value at the same place become a union, and an empty array becomes unknown[]. Numbers are number in TypeScript and integer in the schema when every sample is a whole number. Dates stay as string. The schema lists required keys and types; it does not forbid extra properties. Check the result against more data before relying on it.