Skip to content
17×7

JSON Schema Generator

Paste a sample JSON document and get a draft 2020-12 schema back — types, nested properties, required keys and string formats — then watch your sample pass it.

  • Runs in your browser
  • Draft 2020-12
  • Self-checked against your sample
  • Nothing uploaded
Sample
One representative document. Every element of an array is merged into one item schema. Nothing is uploaded.
Options
Keys found in every object at their position
date-time, date, email, uri, uuid

Results

Showing the example values — change any input to recalculate.

15 propertiesdraft 2020-12 schema · your sample passes it
Generated schema (JSON)
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "id": { "type": "string", "format": "uuid" },
    "createdAt": { "type": "string", "format": "date-time" },
    "customer": {
      "type": "object",
      "properties": {
        "name": { "type": "string" },
        "email": { "type": "string", "format": "email" },
        "website": { "type": "string", "format": "uri" }
      },
      "required": ["name", "email", "website"]
    },
    "items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "sku": { "type": "string" },
          "qty": { "type": "integer" },
          "price": { "type": "number" },
          "giftNote": { "type": ["string", "null"] },
          "size": { "type": "string" }
        },
        "required": ["sku", "qty", "price", "giftNote"]
      }
    },
    "tags": { "type": "array" },
    "shipBy": { "type": "string", "format": "date" },
    "paid": { "type": "boolean" }
  },
  "required": ["id", "createdAt", "customer", "items", "tags", "shipBy", "paid"]
}
Sample
object with 7 keys
Top-level type
object
Required keys
14 of 15
Formats detected
5
Mixed-type positions
1
Deepest level
3
Schema size
35 lines · 1,101 bytes
Self-check
Valid

Test other documents against this schema in the JSON Schema validator.

Show the math (6 steps)
  1. Sample parsed = object with 7 keysJSON.parse on 446 characters
  2. Values visited = 224 objects + 2 arrays + 16 other values, each folded into its position
  3. Properties described = 15distinct key positions, with every array's elements merged into one item schema
  4. Required keys = 14 of 15keys present in every object at their position (the intersection)
  5. Formats added = 5uuid at #/id, date-time at #/createdAt, email at #/customer/email, uri at #/customer/website, date at #/shipBy
  6. Self-check = validthe sample validated against the generated schema (draft 2020-12, formats asserted)
  • 1 key is missing from some objects at the same position, so not required: #/items/*/size.
  • #/items/*/price mixes whole numbers and decimals, so it is typed "number" (which also accepts whole numbers).
  • #/items/*/giftNote holds string and null values, so it gets a type list: ["string", "null"].
  • #/tags is an empty array in the sample, so its items are not described. Add one element to the sample to describe them.

Quick answer

From the sample {"id": 1, "email": "a@example.com", "tags": ["x"], "active": true} the generator writes a draft 2020-12 schema in which id → integer, email → string (format email), tags → array, active → boolean, and lists every key as required. It infers types and common formats from the values it sees; limits such as minimum, maximum or enum have to be added by hand.

About the JSON Schema Generator

This generator turns one sample JSON document into a JSON Schema (draft 2020-12) that describes it. It reads every value once, works out the type at each position — telling whole numbers from decimals — and writes nested properties, required lists and array items. When an array holds many records, they are merged into a single item schema, so keys that only some records have come out as optional. It can also recognize common string formats, lock objects against unknown keys and copy sample values into examples.

The last step is a self-check: your sample is validated against the schema it produced, using the same validator that runs the JSON Schema validator on this site. Everything happens in your browser; the JSON you paste is never uploaded.

How to use it

Paste a JSON document into the Sample box — an API response, a config file, a webhook payload, a database row exported as JSON. The schema updates as you type. If the text is not valid JSON, an error message under the box names the parser's first complaint — most often a trailing comma, a single-quoted string or a comment, none of which JSON allows.

Give the generator the most representative sample you have. If a field is sometimes missing or sometimes null, include records that show it; a single record can only describe itself. Arrays of records make the best input, because every element is merged into the same item schema.

Then set the options. Required keys marks the keys that appear in every object at their position, or leaves required lists out entirely. String formats adds format when every string at a position is a date-time, date, email address, web URL or UUID. Reject unknown keys adds additionalProperties: false to each object that has properties. Copy sample values into examples adds up to three distinct values to each string, number and boolean.

Copy the result with the Copy button above the schema. The notes underneath list each judgment call the generator made — optional keys, mixed types, empty arrays, dictionaries — so you know what to review. The Copy link button saves your options in the address but never the JSON itself.

Use cases

  • Documenting an API response. Paste a real response and get a contract you can review in a pull request or drop into the components of an OpenAPI 3.1 file, whose schema objects follow draft 2020-12.
  • Validating config files. Generate a schema from a known-good config, tighten it, and check each new version before it ships. YAML configs can go through the YAML to JSON converter first.
  • Describing tools for an AI agent. Function-calling and structured-output interfaces declare their arguments as JSON Schema; starting from a real example is faster than writing one from nothing, and the self-check confirms the example really fits what you hand over.
  • Contract tests. Fail a test when an upstream service drops a field, renames it or turns a number into a string.
  • Auditing webhook payloads. Paste several deliveries as one array and see which fields the sender includes every time and which come and go.

How the generator reads your sample

The generator walks the parsed document once. Each value is folded into the shape of its position: the root, each property path, and one shared position for all the elements of an array. A position remembers which JSON types appeared there, how many objects passed through it and how often each key showed up. When the walk is done, every position becomes one schema under five rules:

whole numbers only → "integer" · any fraction → "number"

required = keys present in every object at that position

several types, at most one needing keywords → "type": [ … ]

several types that each need keywords → "anyOf": [ … ]

over 200 distinct keys, most seen once → "additionalProperties": { … }

The number rule follows the specification, which counts any number with a zero fractional part as an integer, so 3.0 is an integer too. As soon as one value at a position has a fraction, the position becomes number, which still accepts whole numbers. A position where only null was ever seen gets the type null, along with a note asking for a better sample.

Formats are added cautiously. A string must match a narrow pattern — an RFC 3339 date-time with in-range hours, minutes and seconds and an explicit offset no larger than ±23:59, a calendar date, a plain email address, an http or https URL, or a hyphenated UUID — and also pass the validator's own format check. Every string at the position has to agree on the same format; one odd value switches the format off there rather than producing a schema your own data would fail.

Some objects are not records but dictionaries: maps keyed by user ids, SKUs, dates or file paths, where each object carries a different set of keys. Listing every key as a property would give a schema thousands of lines long that still rejects the next new id. So when an object position has more than 200 distinct keys and most of them appear in only one object, the generator merges all of its values into a single schema under additionalProperties, which accepts any key, and says so in the notes. Paths below such a position write the key as {key}.

Finally, the sample is validated against the generated schema with formats enforced. Because the schema was derived from the sample, the answer should always be valid. The check exists to prove that on your exact input instead of asking you to take it on trust. Two cases skip it, and the result then says skipped, never valid: a sample whose check would cost more than 2,000,000 property checks and could freeze the page for seconds, and a key containing a lone UTF-16 surrogate, for which the validator cannot build a path.

Worked example: records with different keys

Three order lines. The first has no size, and the third has size set to null:

[
  { "sku": "MUG-11OZ", "qty": 2, "price": 14.5 },
  { "sku": "TEE-L", "qty": 1, "price": 22, "size": "L" },
  { "sku": "CAP", "qty": 3, "price": 18, "size": null }
]

The generator returns:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "array",
  "items": {
    "type": "object",
    "properties": {
      "sku": { "type": "string" },
      "qty": { "type": "integer" },
      "price": { "type": "number" },
      "size": { "type": ["string", "null"] }
    },
    "required": ["sku", "qty", "price"]
  }
}

sku, qty and price appear in every record, so they are required. size appears in two of the three, so it is optional, and because it held a string once and null once its type is the list ["string", "null"]. price mixes 14.5 with whole numbers, so it is number; qty only ever held whole numbers, so it is integer.

Worked example: when anyOf appears

Here an event's when is a single date in one record and a start-and-end object in the other:

[
  { "title": "Launch", "when": "2026-11-02" },
  { "title": "Workshop", "when": { "start": "2026-11-09", "end": "2026-11-10" } }
]

With format detection on, the schema for when is:

{
  "anyOf": [
    {
      "type": "object",
      "properties": {
        "start": { "type": "string", "format": "date" },
        "end": { "type": "string", "format": "date" }
      },
      "required": ["start", "end"]
    },
    { "type": "string", "format": "date" }
  ]
}

Both forms carry their own keywords — the object has properties and required, the string has format — so the generator gives each type its own anyOf branch. Switch format detection off and the string has nothing left to say, so the same field collapses to a type list with the object's keywords beside it:

{
  "type": ["object", "string"],
  "properties": {
    "start": { "type": "string" },
    "end": { "type": "string" }
  },
  "required": ["start", "end"]
}

Folding the anyOf version into one type list would not change what it accepts, because properties only ever applies to objects and format only to strings. The split is there to keep each type's rules together, which makes the schema easier to read and to edit later.

Tightening the schema by hand

A sample shows what a document looked like once, not every value it is allowed to take. Before you rely on the schema, read it alongside the notes and add what only you know: enum for fields with a fixed set of values, minimum and maximum for quantities, minLength or pattern for codes and identifiers, and title and description so other people understand each field. If an array is really a fixed-length tuple, such as a latitude and longitude pair, replace items with prefixItems. Then run a handful of real documents — including some that ought to fail — through the JSON Schema validator.

Limits

  • It cannot infer enums, ranges, string lengths, patterns or descriptions, because a sample does not reveal them.
  • Arrays are treated as lists, so every element shares one item schema. Tuples come out as a merged item schema.
  • Numbers are read as JavaScript numbers. Integers larger than 253 lose precision while parsing, although they are still typed integer.
  • In draft 2020-12, format is an annotation by default, and many validators only enforce it when format assertion is switched on. The validator on this site enforces it.
  • A single object with more than 200 keys also counts as a dictionary, because one object cannot show which keys recur. If it is really a record with that many named fields, list its properties by hand.
  • Numbers beyond the range of a double, such as 1e400, are read as Infinity. They are typed number and kept out of examples.
  • Samples are limited to 1,000,000 characters and 100 levels of nesting. Strings longer than 120 characters are not copied into examples.
  • The schema itself is capped at 1,000,000 characters so the page stays responsive. A deep sample whose shape keeps changing can produce a schema many times its own size, most of it indentation, because every distinct position gets its own entry. Past the cap the schema is printed compactly on one line; if even that is too long, you are asked for a smaller sample.

Sources

Frequently asked questions

Which version of JSON Schema does it produce?

Draft 2020-12, declared in the $schema keyword at the top. Every keyword it writes — type, properties, required, items, additionalProperties, anyOf, format and examples — also exists in draft-07 with the same meaning, so for an older validator you can swap the $schema URI for the draft-07 one. The uuid format was only named in draft 2019-09, so older validators treat it as unknown.

Why is a key missing from the required list?

Because at least one object at that position did not have it. When an array holds several records, a key is only required if every record has it — the intersection of their keys. The notes under the result list the optional keys by path (the first five, then a count), where * stands for every element of an array. If you know a field is mandatory, add it to required by hand.

When does a field become integer rather than number?

When every value seen at that position is a whole number. JSON has a single number type, and draft 2020-12 counts any number with a zero fractional part as an integer, so 3 and 3.0 both qualify. One value with a fraction, such as 2.5, makes the whole position number, which still accepts whole numbers.

How are null values handled?

A null next to another type adds null to the type list — for example ["string", "null"] for a field that is sometimes text and sometimes empty. A field that is null in every sample gets the type null on its own, with a note suggesting a better sample, because otherwise the schema would reject the field as soon as it is filled in.

Does my JSON leave the browser?

No. Parsing, schema generation and the self-check all run in JavaScript on this page, with no network requests. The copy-link button stores the options in the address but never the JSON, so a shared link does not carry your data.

Why do I get anyOf instead of a list of types?

A type list is used whenever at most one of the types at a position needs keywords of its own, such as an object with properties next to null. When two or more types each need keywords — an object with properties and a string with a format, say — the generator gives each type its own anyOf branch so its rules stay together.

Why did an object get additionalProperties instead of properties?

Because it looked like a dictionary rather than a record: more than 200 distinct keys at that position, most of them in only one object. Maps keyed by ids, SKUs or dates are the usual case. Listing every key would give a huge schema that still rejects the next new id, so the generator merges the values into one schema and allows any key. If the object really is a record with that many named fields, write its properties by hand.

Can it detect enums, minimum values or patterns?

No, on purpose. A sample containing "small" and "large" cannot tell whether "medium" is also allowed, and a price of 14.5 says nothing about the real minimum. Guessing would produce a schema that rejects valid data. Add those constraints once you know the rules; the examples option is the safe way to record what the sample contained.

Disclaimer. The schema describes only what your sample contains and is a starting point to review, not a full contract; other validators may treat formats and draft differences differently.

Last updated . Found a mistake? Tell us — corrections ship the same week.