JSON Schema Generator and Validator
Generate a JSON Schema from a sample document, or validate JSON against a schema, with readable explanations of exactly which rule failed and where.
Paste your most complete example and you get the worst schema
The instinct, handed a box like this, is to paste the richest record you have — the one with every field filled in. That is precisely the wrong choice, and not by a small margin. Inference can only see what is in front of it, so every key present in your sample becomes a required key. The more complete the example, the more the schema insists on, and the more of your real data it turns away.
Over 3,000 random families of 8 records, each optional field present about half the time:
| Keys in the pasted sample | Share of the family it accepts |
|---|---|
| 2 | 100.0% |
| 3 | 56.8% |
| 4 | 34.8% |
| 5 | 23.4% |
| 6 | 17.7% |
| 7 | 14.9% |
The sample with the fewest keys gave the most permissive schema in 95% of families. The sample with the most keys did so in 0.1%. Not a tendency — a reversal.
This is not the tool misbehaving. When every record in a family genuinely has the same keys, all 500 control families produced one identical schema that accepted everything. The entire effect comes from optional keys, which is the one thing a single example cannot tell you: whether a field is absent because it is optional, or absent because this row happens not to have it.
Five records of the same shape, and no schema that fits them
Here are 5 records any human would call one shape. Generating a schema from each in turn and validating all five against it:
| Schema built from | Keys | Records accepted |
|---|---|---|
{"id":1,"name":"Ada","email":"ada@example.com","tags":["a"],"score":10} | 5 | 2 of 5 |
{"id":2,"name":"Bob","tags":[],"score":7} | 4 | 4 of 5 |
{"id":3,"name":"Cy","email":"cy@example.com","tags":["a","b"],"score":8.5} | 5 | 3 of 5 |
{"id":4,"name":"Dee","email":null,"tags":["c"],"score":9} | 5 | 1 of 5 |
{"id":5,"name":"Eve","email":"eve@example.com","tags":["d"],"score":6,"admin":true} | 6 | 1 of 5 |
None of them accepts all five. The best manages 4 — and it is record 2, the one with a field missing. Its gap is the reason it wins: a key that is not there cannot be required.
The fullest record fails three separate ways, and they are worth separating because only the first is obvious:
| Inferred | Value | Turns away |
|---|---|---|
required | ["id","name","email","tags","score"] | record 2, which has no email |
score | {"type":"integer"} | record 3, where the score is 8.5 |
email | {"type":"string"} | record 4, where the email is null |
The second one is the quiet killer. JSON has a single number type and no way to write 5.0
differently from 5, so a price that happens to be exactly 5 in your sample infers
integer — and the next record, at 5.99, is rejected by a schema nobody
meant to write.
How many samples would be enough
Reassuringly few. Intersecting the key lists as records arrive — a key stays required only while every record so far has had it — the required list narrows to the genuinely always-present keys after a median of 3 records.
| Records seen | Families settled |
|---|---|
| 3 | half |
| 6 | nine in ten |
| 12 | all of them |
One sample is never enough; a handful nearly always is. So the working method is: generate from
the sparsest record you have, then read the required list and delete
anything you know to be optional. That is a thirty-second edit, and it is the whole difference
between a schema that describes your data and one that describes a single row of it.
Where it declines to guess
| Input | Inferred |
|---|---|
[] | {"type":"array","items":{}} |
["a"] | {"type":"array","items":{"type":"string"}} |
[1, "a"] | {"type":"array","items":{"anyOf":[{"type":"integer"},{"type":"string"}]}} |
[1, 2, 3] | {"type":"array","items":{"type":"integer"}} |
Two behaviours here are worth crediting, because both are the tool refusing to over-claim. A
mixed array produces anyOf listing every type it saw rather than picking the first
one. And an empty array produces no constraint at all — which is the honest reading of a
list that contains no evidence about what belongs in it. Compare that with the required list,
where the same missing evidence is treated as certainty.
How to use
- Paste a sample document to generate a schema from it.
- Or paste a schema and a document to validate.
- Read which rule failed and at which path.
- Tighten the generated schema by hand before relying on it.
Frequently asked questions
What is JSON Schema for?
Describing the shape a JSON document must take, so it can be validated automatically. It defines which fields are required, what types they hold, and what values are acceptable — turning assumptions about a payload into something a machine can check.
Is a generated schema good enough to use as-is?
Rarely. Generation can only infer structure from the example you gave it, so it will mark every field it saw as the type it happened to be, miss optional fields entirely, and impose no meaningful constraints on values. Treat it as a first draft to tighten by hand.
Why are fields optional by default?
Because JSON Schema treats a required list as an explicit statement rather than an inference. Any field not named in the required array may be absent, which surprises people whose validation passes on a document missing half its content.
What is additionalProperties for?
Controlling whether fields not mentioned in the schema are allowed. It defaults to permitting them, so a schema will happily accept a document containing unexpected keys. Setting it to false catches typos in field names, which is often exactly what you want.
Which version should I use?
Draft 2020-12 is current, and most actively maintained libraries support it. Older drafts remain widespread, and there are genuine incompatibilities between them, so the schema should declare its draft explicitly rather than relying on a library's default.
Can a schema validate relationships between fields?
To a degree. Conditional keywords let you say that if one field has a certain value, another becomes required. Anything more involved — a date being after another date, or a total matching a sum — is beyond what the language expresses and belongs in application code.
🔒 This tool runs entirely in your browser. Nothing you enter is uploaded, logged, or stored.