JSON Schema Validation, Explained
After reading this you will be able to read a JSON Schema, predict which documents it accepts, and interpret every violation the validator reports by its exact path.
What JSON Schema does and one quick example
A JSON Schema is a JSON document that describes the shape of other JSON documents. It states what types are allowed, which fields must exist, which values are permitted, and how nested arrays and objects must look. The validator reads your data, walks it against the schema, and reports each place the data breaks a rule.
Here is a schema that accepts an object with a required string name and an integer age of at least 0:
{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"integer","minimum":0}},"required":["name"]}
The data {"name":"Ada","age":36} passes. The data {"age":-1} fails twice: name is missing, and age is below the minimum. The validator names both failures with a path so you know exactly where to look.
JSON Schema is a specification, not a library. The syntax here follows draft-07, the most widely deployed version. Later drafts (2019-09, 2020-12) rename a few keywords but keep the same model.
When to validate, and when not to
Validate at trust boundaries. An API endpoint that receives JSON from a client should validate before it touches a database. A config file loaded at startup should validate before the program acts on bad settings. A message pulled off a queue should validate before a worker processes it. In each case the cost of a bad document is high and the source is not fully under your control.
Do not validate data you just produced yourself in the same function. If you built an object three lines above from typed local variables, a schema check adds latency and maintenance for no gain. Validation earns its keep where the data crosses from an untrusted or loosely typed world into a strict one.
If your goal is only to check that text is well-formed JSON, you do not need a schema at all. Use the JSON Formatter & Validator for that. A schema answers a stronger question: is this JSON the right JSON.
The model: a schema is a tree of constraints
Validation is not one test. It is many small tests, each applied at a specific location in your document. Think of the schema and the data as two trees walked in parallel. At each node the validator collects the keywords that apply and checks every one. A node passes only if it satisfies all of them.
The applicable keywords fall into groups. For any value, type, enum and const apply. For numbers, minimum, maximum, exclusiveMinimum, exclusiveMaximum and multipleOf. For strings, minLength, maxLength, pattern and format. For arrays, items, minItems, maxItems and uniqueItems. For objects, properties, required, additionalProperties, minProperties and maxProperties.
Two facts follow from this model and trip up most beginners. First, a keyword that does not apply to the current type is silently ignored. Apply minimum to a string and nothing happens. Second, a missing keyword means "no constraint," not "forbidden." A schema with only {"type":"object"} accepts an object with any properties whatsoever.
The combinators: allOf, anyOf, oneOf, not
The four boolean combinators let you build compound rules. Each takes a subschema or a list of subschemas and counts how many the data satisfies. Let n be the number of subschemas in a list and m the number the data matches.
Read it plainly. allOf passes when the data matches every subschema (m = n). anyOf passes when it matches at least one (m \ge 1). oneOf passes when it matches exactly one (m = 1), so it fails both when zero match and when two or more match. not passes when the data matches none (m = 0).
The common mistake is using oneOf where anyOf is meant. Suppose you write oneOf with two branches, one requiring a number "at least 10" and one requiring "a multiple of 5." The value 20 satisfies both, so m = 2 and oneOf rejects it. If you only meant "one or the other," use anyOf.
A worked example reproducing the demo
Validating a user record
The demo loads a schema for a user object and a sample document. The schema requires an id, a name and a valid email, allows an optional age from 0 to 120, and requires roles to be a non-empty array of strings drawn from a fixed set.
Schema (abbreviated): {"type":"object","required":["id","name","email"],"properties":{"id":{"type":"integer","minimum":1},"name":{"type":"string","minLength":1},"email":{"type":"string","format":"email"},"age":{"type":"integer","minimum":0,"maximum":120},"roles":{"type":"array","minItems":1,"items":{"type":"string","enum":["admin","editor","viewer"]}}},"additionalProperties":false}
Now take a document with three deliberate errors:
{"id":0,"name":"Ada","email":"ada[at]example.com","age":150,"roles":["admin","guest"],"nick":"A"}
- At
/id: value is0, butminimumis 1. Violation. - At
/email:ada[at]example.comis not a valid email format. Violation. - At
/age:150exceedsmaximum120. Violation. - At
/roles/1:guestis not in the enum["admin","editor","viewer"]. Violation. - At the object root:
nickis present butadditionalPropertiesisfalse. Violation.
Five violations, each with a path. Fix them to {"id":1,"name":"Ada","email":"[email protected]","age":50,"roles":["admin"]} and the validator reports no errors.
Reading and interpreting the results
Every violation carries three things: a path, the failing keyword, and the offending value. The path uses JSON Pointer style, so /roles/1 means "the item at index 1 inside the array named roles." An empty path or / refers to the whole document. Learn to read the path first; it takes you straight to the node without scanning the file.
Order matters less than you expect. The validator reports all violations it finds, not just the first, so a long list often traces back to one structural mistake. If ten errors all sit under /items/3, the fourth array element is malformed, and fixing its shape usually clears the whole cluster.
When additionalProperties is false, a single typo in a key name produces two errors: one for the unexpected key and one for the required key that now appears missing. Check spelling before you add fields.
Common mistakes
Forgetting that required is separate from properties. Listing a field under properties describes it if present. It does not force it to appear. Add the field name to the required array to make it mandatory.
Expecting type to coerce. The string "36" is not an integer. If your data arrives from a form or a query string where everything is text, either declare the fields as strings with a pattern, or convert before validating.
Confusing integer and number. number accepts 3.5; integer rejects it. Note that 2.0 counts as an integer in JSON Schema because its value has no fractional part.
Assuming format always enforces. In draft-07, format is an annotation that some validators check and others ignore. This tool checks the common formats (email, date, uri and others), but do not treat a passing format as a security guarantee.
Using remote $ref. A $ref to a URL is not resolved here. Inline the referenced schema, or use a local $ref that points inside the same document with #/definitions/....
Related tools on this site
Before you validate, make sure the text parses. The JSON Formatter & Validator pretty-prints and checks JSON, and lets you query it with JSONPath. If your data lives in another format, the data converter turns YAML, TOML, XML and CSV into JSON first. When a schema keeps rejecting a field, the Regex Tester helps you get the pattern right with a plain-English breakdown, and the String Escaper fixes quoting problems inside string values.
To compare two documents that should match a schema, use the JSON Diff. To produce test payloads that fit a schema you are designing, the Mock JSON Generator builds sample responses from a template.
Frequently asked questions
Does an empty schema accept anything?
Yes. The schema {} and the schema true both accept every valid JSON value. The schema false rejects every value. Constraints are additive: each keyword you add can only narrow what passes.
Why does my valid-looking object fail with "additional property"?
Because additionalProperties is set to false and your object has a key not listed under properties. Either add the key to properties, or set additionalProperties to true to allow extra keys.
What is the difference between anyOf and oneOf?
anyOf passes when the data matches at least one branch. oneOf passes only when it matches exactly one. If a value can legitimately satisfy two branches at once, use anyOf; oneOf would reject it.
Does the tool support draft 2020-12?
The engine follows draft-07 semantics. Most schemas written for later drafts work if they avoid the renamed array keywords prefixItems and the changed $ref resolution. Convert those to their draft-07 equivalents before validating.
Is my data sent anywhere?
No. Validation runs entirely in your browser. Nothing you paste leaves your device.