JSON Schema Generator
Infer a schema from a real document, reading every record
JSON
JSON Schema
Why the first record is not enough
You have a response from an API and you need a schema for it — for a contract test, for a validation step in a pipeline, or because someone asked what the endpoint returns and pointing at an example is not an answer. Writing it by hand from a 200-line payload is an afternoon you would rather spend elsewhere.
Almost every tool that does this reads the first record and writes down what it sees. That produces a schema which passes on the sample you fed it and fails a fortnight later on real traffic, which is worse than having no schema at all, because now the failure arrives in CI with a message about additionalProperties and everyone blames the validator.
The difference here is that an array is treated as evidence rather than as a container. Every element is read, and the results are merged. If apn is a string in the first two records and null in the third, the output says "type": ["string", "null"], because that is what the data said. If a property is absent from any record, it does not go into required. Those two decisions are where inferred schemas usually go wrong, and both of them are only visible if you look past element zero.
Long integers are the other trap, and it is a quiet one. A 19-digit ICCID or a snowflake ID cannot survive an ordinary parse — JavaScript numbers are doubles, so 8944501012345678901 comes back as 8944501012345678900. Several schema tools work around that by treating anything oversized as a string, which then puts "type": "string" in your schema for a field the API plainly sends as a number. This page keeps the digits and the type, and RFC 8259 section 6 is worth reading on why the two are separable at all: JSON itself puts no limit on a number, only the parser does.
Nothing is uploaded. The document stays in the browser, which matters when the payload you are describing came out of production.
How to use it
- Paste a real document – An array of records works best — the more records, the better the schema, because each one is another piece of evidence about what varies.
- Pick a draft – 2020-12 is the current one and what OpenAPI 3.1 aligns with. Choose draft-07 if the validator you are feeding predates it, which many still do.
- Decide how strict required should be – The default marks a property required only when every record has it. Switch to all properties when your sample is one canonical record rather than a collection.
- Read the observation line – It names the fields that hold more than one type and the ones that are optional. Those are the fields where the sample taught you something.
- Copy it out and tighten by hand – What comes back is a floor. Add the length limits, the patterns and the enums that you know about and the sample cannot.
Feed it more records than feel necessary. A schema inferred from three rows describes three rows; the same endpoint over a thousand rows will show you a nullable field you did not know about. When you have the schema, check it against a second document with the JSON Schema Validator — that is the cheapest way to find out whether your sample was representative.
Three SIM records, and what they disagree about
The three records below are all valid and none of them is broken. They simply do not agree, in the way real exports never quite do: the third has no apn value, and only the second was ever suspended. Read the first record alone and you get a schema that rejects both of the others.
[
{ "msisdn": "447700900112",
"iccid": 8944501012345678901,
"apn": "internet" },
{ "msisdn": "447700900187",
"iccid": 8944501019876543210,
"apn": null,
"suspendedAt": "2026-08-09T11:05:00Z" },
{ "msisdn": "447700900204",
"iccid": 8944501055512340987,
"apn": "iot.m2m" }
]{
"type": "array",
"items": {
"type": "object",
"properties": {
"msisdn": { "type": "string" },
"iccid": { "type": "integer" },
"apn": { "type": ["string", "null"] },
"suspendedAt": { "type": "string" }
},
"required": ["msisdn", "iccid", "apn"]
}
}
# apn is nullable, suspendedAt is not requiredWhen you actually need this
Documenting an endpoint nobody wrote down
An internal service returns JSON and the only documentation is a wiki page from two years ago. Capture a few hundred real responses, infer a schema, and you have something concrete to argue about in review. It is also the fastest way to discover that a field the team believed was mandatory is missing from four percent of responses.
A contract test that fails before your users do
Once the schema exists, validating each response against it in CI turns a silent shape change into a failing build. Ajv is the usual choice in the JavaScript world and compiles schemas to functions, so the check costs almost nothing per request. The schema this page produces is deliberately permissive so that first run is not drowned in false failures.
Filling in the components section of an OpenAPI document
OpenAPI 3.1 dropped its own dialect and now uses JSON Schema directly, so a schema generated here can be pasted under components/schemas with no translation step. Open the spec afterwards in the OpenAPI Viewer to check that the reference resolved.
Understanding a payload you have been handed
Sometimes the schema is not the deliverable — it is just the fastest way to see the shape of 4,000 lines of JSON. Every property and its type on one screen beats scrolling. If that is what you are after, the tree viewer is the other way to get it.
What it does that a one-pass reader does not
- Merges every element of an array into one
itemsschema instead of describing the first one. - Treats
nullas a type, so a field seen as both a string and null becomes a union rather than quietly losing the null. - Computes
requiredfrom what the records actually share, with the option to override it in either direction. - Collapses
integerandnumberwhen both appear, because a union of the two would reject nothing while looking like it meant something. - Keeps a 19-digit identifier typed as an integer, digits intact.
- Only annotates a
formatwhen every value at that position matches it — one exception drops the annotation for the whole field. - Checks each octet before calling something an IPv4 address, because
999.1.1.1has the right shape and is not an address. - Emits stable output: the same document always produces the same bytes, so the schema can be committed and diffed.
Questions worth answering
Why is there no enum, even though the field only ever has three values?
Because three distinct values in a five-record sample is a small sample, not an enumeration. Writing enum from observation produces a schema that rejects the fourth status code the day it appears, and the failure looks like a bug in the data rather than a bug in the schema. The same reasoning rules out minLength, maximum and additionalProperties: false. Everything here is an observation about the document; everything left out is a guess about intent, and intent is yours to add.
What does the "All properties" required setting actually do?
It marks every property it saw as required, which is right when your input is one canonical record and wrong when it is a collection. Turn it on with records that disagree and the schema will reject the very document it came from — a property present in only one record is now mandatory in all of them. That is the setting working correctly, not a defect, but it surprises people, so it is worth knowing before you use it. The required keyword is about presence only; a property that is present and null still satisfies it.
Which draft should I pick?
2020-12 unless something downstream cannot read it. It is the current release and what OpenAPI 3.1 builds on. Pick draft-07 when your validator is older — a great deal of tooling settled there and never moved, and a schema declaring a $schema the validator does not recognise usually gets treated as draft-07 anyway, silently. Being explicit about it is better than relying on that.
The format annotations are not being enforced. Is that broken?
No — that is the specified default. format is an annotation, and validators ignore it until you ask them not to. In Ajv it takes the ajv-formats package plus opting in. This matters more than it sounds: turning enforcement on later can fail documents that passed for months, which is exactly why nothing here annotates a format unless every single value at that position matched it.
My array came back with no items keyword. Why?
Because it was empty in every record, so there is nothing observed to describe. Writing "items": {} would be true — anything is allowed — but it reads as though something was measured. Leaving it out is the honest version, and it is a signal: it means your sample never exercised that array, which is usually worth fixing before the schema is.
Can I go the other way and get sample data from a schema?
Not on this page. If you have a schema and want to know whether a document satisfies it, that is the JSON Schema Validator, which reports the path of each failure rather than just a pass or fail. To pull one field out of a large response while you work out what to describe, the JSONPath Tester is quicker than scrolling.
Related tools
Further reading
- Draft 2020-12 release notes – What changed from draft-07, including the split of items and prefixItems.
- Understanding JSON Schema – The reference to keep open while you tighten a generated schema by hand.