JSON Schema Validator

Every error at its exact path, by the real rules.

  • Drafts 2020-12 and 7
  • Exact error paths
  • Schema from samples
A small stack of rounded nested trays on a desk with soft labelled tabs, one tray glowing amber where something does not fit.

The guide

A JSON Schema is a contract, and a validator is only useful if it reads the contract exactly

JSON Schema turns an informal promise about data into rules a machine can check. The checking is only trustworthy when every draft, reference and annotation rule is applied as written.

  • 1,302 of 1,302 required 2020-12 tests in the official suite that this tool passes
  • ~0 and ~1 how a JSON Pointer writes a tilde and a slash inside a name
  • 25.7499 fails multipleOf 0.01 by exact decimal arithmetic
  • draft-04 to 2020-12 the five dialects read here, each chosen by the schema's $schema
Read the full guide Fold the guide 7 min · 15 sources

Every program that accepts JSON makes a promise it rarely writes down. It expects an order to have an identifier, a list of lines and a total, and it expects the quantity on each line to be a whole number above zero. When that promise lives only in code, it is enforced in a dozen scattered places and broken quietly in the thirteenth. JSON Schema exists to move the promise into a document of its own, written in JSON, that any machine can read and check.

That is a bigger idea than it first sounds. A schema is not documentation that happens to be machine readable. It is a contract with exact semantics.

Two parties who agree on a schema should agree on whether a given document honours it. The whole value of the contract depends on that agreement, which is why the rules for reading a schema matter as much as the rules it contains.

The format describes the structure of JSON data for validation, documentation and interaction control, and it borrows the self-describing spirit of XML Schema while staying in JSON. Its specification is published as a series of Internet-Drafts, and the latest of them is called 2020-12. That last detail turns out to be the first trap.

Drafts are dialects, and $schema says which one you speak

JSON Schema has changed in ways that alter results, not just spelling. The 2019-09 release renamed definitions to $defs, split dependencies into dependentRequired and dependentSchemas, introduced $anchor for plain-name fragments and added unevaluatedProperties and unevaluatedItems. The 2020-12 release then replaced the array form of items with prefixItems, so that items now means only “every remaining element”, and replaced $recursiveRef with $dynamicRef.

Consider a schema that lists two tuple positions under items. Read as draft-07, it checks the first two elements of an array against two different schemas. Read as 2020-12, the array form of items is not a valid keyword value at all. The same text, the same document, a different verdict. A validator that guesses the draft is not validating; it is improvising.

The $schema keyword settles the question. Its value is a URI naming the meta-schema, and through it the dialect, that the schema was written for. A careful validator reads it first, applies that draft’s keyword set and semantics, and says plainly when it is absent and a default had to be chosen. Older drafts even disagree about small things. In draft-04, an integer was defined as a number written without a fraction or exponent part; in 2020-12, 1.0 counts as an integer because numbers are read as decimal values, not as text. This tool reads every dialect from draft-04 to 2020-12 and applies each one’s rules.

References, base URIs and the dynamic scope

Real schemas are rarely one flat object. They reuse pieces through $ref, which holds a URI reference resolved against the current base URI by the ordinary rules for relative references in RFC 3986. The base itself comes from $id, so a schema can declare where it lives and every relative reference inside it follows. A fragment then points into the target, either as a JSON Pointer or as a plain name declared with $anchor.

Two subtleties catch hand-written validators. In draft-07, every other keyword beside a $ref must be ignored, so a sibling type or $id quietly does nothing. From 2019-09 on, $ref became an applicator like any other, and its siblings apply together with it. A validator that treats both eras alike will accept documents in one draft that it should reject, or the reverse.

The second subtlety is $dynamicRef. It is resolved once statically, but if its target carries a $dynamicAnchor of the same name, the final target is chosen at evaluation time: the outermost schema resource in the dynamic scope that defines that anchor wins. This is how a meta-schema can be extended without copying it, and it is why the 2020-12 meta-schema itself cannot be evaluated correctly by a validator that only knows static references.

Nothing in the specification requires fetching a remote URI, and fetching one is a privacy and reliability problem in a browser. A reference to a schema that has not been loaded should therefore be reported as unresolved, with the URI it needed, rather than silently passing. This tool resolves references only among the schemas you paste or open, by their $id, and lists every one it could not find.

Why unevaluatedProperties is not additionalProperties

The oldest way to forbid unexpected properties is additionalProperties set to false. Its reach is deliberately short: it sees only the properties and patternProperties keywords in the same schema object. Put a property definition inside an allOf branch and additionalProperties cannot see it, so it rejects the very property the branch allows.

The keyword unevaluatedProperties was added to fix exactly that. It works from annotations: each successful properties, patternProperties and additionalProperties keyword records which names it evaluated, and those records flow upward through allOf, anyOf, oneOf, if, then, else, dependentSchemas and references. Only names that nothing evaluated are left for unevaluatedProperties to judge. Annotations from a failed subschema are dropped, so a branch of anyOf that did not match cannot vouch for a property.

The difference is easy to show. Take a schema whose own properties keyword allows a, with an allOf branch that allows b, and give it the object with both. With additionalProperties false, b is rejected at /b. With unevaluatedProperties false, the object is valid. Both behaviours are correct for their keyword, which is the point: they answer different questions.

Formats, numbers and the exact place of every fault

The format keyword names a semantic type such as date-time, email or uuid. In 2020-12 it is an annotation by default: a validator collects the value and may report it, but the document does not fail because of it. A separate format-assertion vocabulary turns it into a real check, and a validator may also offer assertion as an option. This matters because format checks are both useful and treacherous. RFC 3339 allows a leap second, but only at the end of a minute that is 23:59 in UTC, and an international hostname needs character tables that most validators do not carry. An honest tool says which formats it checks and names the ones it does not.

Numbers hide a quieter trap. JSON writes numbers as decimal text, and RFC 8259 leaves their precision to implementations. Many languages read them as binary floating point, where 0.0075 divided by 0.0001 leaves a remainder of 0.00009999999999999937 instead of zero. A validator that tests multipleOf that way rejects a value that is plainly a multiple. Comparing the decimal digits as written, scaled to a common exponent, gives the right answer every time, and the test for this tool compares two thousand such cases against an arbitrary precision decimal library.

Patterns have their own dialect. The specification says regular expressions should follow ECMA-262, and the u flag puts them in Unicode mode, where a character outside the Basic Multilingual Plane counts as one character and some sloppy escapes become errors. String lengths are counted the same way, in code points rather than in the UTF-16 units a browser stores.

Once a validator has found a fault, it has to say where. The 2020-12 output format gives each error an instanceLocation and a keywordLocation, both JSON Pointers, plus an absoluteKeywordLocation that names the exact keyword in its schema resource. A JSON Pointer is a list of names and indexes separated by slashes, and RFC 6901 escapes the two awkward characters inside a name as ~0 and 1: the first stands for a tilde, the second for a slash, so a property called a/b becomes /a1b.

The example on this page shows what that buys. The order it loads breaks five rules. The second line item has a quantity of 0, so the report points to instance location /items/1/qty, keyword location /properties/items/items/$ref/properties/qty/minimum, and absolute location urn:example:order-v1#/$defs/line/properties/qty/minimum. The keyword location records the path through the reference; the absolute one names the keyword where it is written. Its total of 25.7499 fails multipleOf 0.01, and a notes field fails unevaluatedProperties at /notes. In a document of fifty thousand lines, those three strings find the fault without scrolling.

Inferring a schema, and why not to trust it blindly

Writing a schema from nothing is tedious, so a natural shortcut is to infer one from examples. The method is straightforward: walk every sample, record the types seen at each path, mark a property as required when it appears in every sample, call a number an integer when no sample had a fraction, and merge array elements into one item schema. A path with only a few repeated string values can be offered as an enum.

Inference describes the samples and nothing more. Three orders that all paid by card will produce a schema in which a bank transfer looks impossible, and a field that happened to be present each time becomes required. Every sample is valid against the schema inferred from it, and this tool checks that after every inference, but validity against its own evidence is a low bar. Treat the result as a draft to edit, then validate real documents against it.

The same caution applies to validation itself. A validator can show that a document honours a schema; it cannot show that the schema says what you meant. That is why conformance is measured, not claimed. The official JSON-Schema-Test-Suite, released under the MIT licence, collects thousands of small cases, each a schema, an instance and the expected verdict. This tool passes every required case for all five drafts it reads, including 1,302 of 1,302 for 2020-12, and the page lists by name the optional cases it does not pass, with the reason for each.

What remains is judgment. A schema that rejects every bad document but also rejects the good ones you forgot to imagine is a broken contract, and no validator can find that for you.

Read the errors, follow each pointer to its exact place, and when a rule surprises you, check which draft you are speaking.

Sources

Every source consulted for this guide. Links checked October 25, 2026.

  1. JSON Schema: A Media Type for Describing JSON Documents JSON Schema (json-schema.org), draft 2020-12
  2. JSON Schema Validation: A Vocabulary for Structural Validation of JSON JSON Schema (json-schema.org), draft 2020-12
  3. JSON Schema: A Media Type for Describing JSON Documents IETF Datatracker, draft-handrews-json-schema-01 (draft-07)
  4. JSON Schema Validation: A Vocabulary for Structural Validation of JSON IETF Datatracker, draft-handrews-json-schema-validation-01 (draft-07)
  5. JSON Schema - 2020-12 Release Notes JSON Schema (json-schema.org)
  6. JSON Schema - 2019-09 Release Notes JSON Schema (json-schema.org)
  7. JSON-Schema-Test-Suite: A language agnostic test suite for the JSON Schema specifications json-schema-org on GitHub (MIT licence)
  8. JSON Schema: core definitions and terminology IETF Datatracker, draft-zyp-json-schema-04 (draft-04)
  9. RFC 8259: The JavaScript Object Notation (JSON) Data Interchange Format IETF, RFC Editor
  10. RFC 6901: JavaScript Object Notation (JSON) Pointer IETF, RFC Editor
  11. RFC 3986: Uniform Resource Identifier (URI): Generic Syntax IETF, RFC Editor
  12. RFC 3339: Date and Time on the Internet: Timestamps IETF, RFC Editor
  13. ECMAScript 2025 Language Specification Ecma International, TC39
  14. RegExp.prototype.unicode MDN Web Docs (CC BY-SA 2.5)
  15. JSON - Wikipedia Wikipedia (CC BY-SA 4.0)