JSON to JSON Schema
The result appears here.
Generate
Your document never leaves the browser.
A JSON Schema inferred from your sample — 2020-12 or draft-07, never uploaded.
Writing a schema by hand for a large payload is slow and error-prone. Paste an example document instead and start from a schema that already accepts it, then tighten the parts you know more about than the sample does.
Each distinct object shape becomes an entry under $defs, named as the other languages name their types, and the root is a $ref to its own entry. A key goes in required when every record that could hold it does, so sku, qty, price and note are required on Item, and gift-wrap is not.
Numbers are split the way JSON Schema can split them: qty never has a fraction, so it is integer, while price holds 3.5 and is number. note is null in one line item and text in the other, so its type is the array ["null", "string"]. Every object closes with additionalProperties: false, so a key the sample never had is reported as an error.
The sample, an order with two line items, pasted as the source
{
"order_id": 1042,
"placed_at": "2026-09-25T10:15:00Z",
"paid": true,
"customer": { "name": "Ada Lovelace", "email": "[email protected]" },
"items": [
{ "sku": "PEN-01", "qty": 2, "price": 3.5, "note": null },
{ "sku": "INK-07", "qty": 1, "price": 12, "note": "Fragile", "gift-wrap": true }
]
}The 2020-12 schema written for it
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$ref": "#/$defs/Root",
"$defs": {
"Customer": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"email": {
"type": "string"
}
},
"required": [
"name",
"email"
],
"additionalProperties": false
},
"Item": {
"type": "object",
"properties": {
"sku": {
"type": "string"
},
"qty": {
"type": "integer"
},
"price": {
"type": "number"
},
"note": {
"type": [
"null",
"string"
]
},
"gift-wrap": {
"type": "boolean"
}
},
"required": [
"sku",
"qty",
"price",
"note"
],
"additionalProperties": false
},
"Root": {
"type": "object",
"properties": {
"order_id": {
"type": "integer"
},
"placed_at": {
"type": "string"
},
"paid": {
"type": "boolean"
},
"customer": {
"$ref": "#/$defs/Customer"
},
"items": {
"type": "array",
"items": {
"$ref": "#/$defs/Item"
}
}
},
"required": [
"order_id",
"placed_at",
"paid",
"customer",
"items"
],
"additionalProperties": false
}
}
}A user nested in the root
{ "user": { "id": 7 } }With Draft draft-07 and allow extra properties on
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$ref": "#/definitions/Root",
"definitions": {
"User": {
"type": "object",
"properties": {
"id": {
"type": "integer"
}
},
"required": [
"id"
],
"additionalProperties": true
},
"Root": {
"type": "object",
"properties": {
"user": {
"$ref": "#/definitions/User"
}
},
"required": [
"user"
],
"additionalProperties": true
}
}
}Option
Draft: 2020-12, the default, with its definitions under $defs, or draft-07, with them under definitions, for validators and tools that stop at the older draft.
Option
allow extra properties: off by default. Turn it on to write additionalProperties: true, for payloads that are allowed to grow new keys without failing validation.
An inferred schema is a starting point, not a specification. It cannot know that qty must be at least 1, that email follows a format, or that a status only takes three values. Add minimum, format, pattern and enum where they apply, and give fields a description while you are there.
To test the result, open the JSON Schema Validator, paste the schema beside another document, and every violation is listed with its path. A second real example is the quickest way to find a key the first one did not show optional enough.
A quantity, an email and a status
{ "qty": 1, "email": "[email protected]", "status": "paid" }The schema accepts them, and says nothing more about them
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$ref": "#/$defs/Root",
"$defs": {
"Root": {
"type": "object",
"properties": {
"qty": {
"type": "integer"
},
"email": {
"type": "string"
},
"status": {
"type": "string"
}
},
"required": [
"qty",
"email",
"status"
],
"additionalProperties": false
}
}
}Inference and code generation both run inside this browser tab. The sample is never uploaded, stored on a server or logged, because the page has no server to send it to, and once loaded it carries on working with the connection off.
The 2020-12 draft, unless a tool you depend on only reads draft-07. OpenAPI 3.1 uses 2020-12; many older validators and editors expect draft-07.
No. Those three follow each provider’s own rules, such as which keys must be required, and have pages of their own.
No. One sample cannot tell a fixed list of values from free text, so strings stay string. Add enum and format yourself.