Quick answer: The most damaging JSON Schema validation mistakes come from assuming that keywords do more than they actually specify. Declared properties remain optional without
required; unknown fields are allowed by default;formatmay only annotate;oneOfcan match multiple branches; anddefaultdoes not universally insert values. Test the active draft and production validator together.
A schema can compile successfully and still express the wrong contract. That is the uncomfortable part: the JSON is valid, the schema is valid, and unwanted data still reaches the application. The eight checks below focus on those quiet failures rather than obvious syntax errors.
Inspect the JSON structure in the JSON Viewer. This helps with reading the payload; schema validation still requires a separate schema validator.
1. Did you forget to declare the JSON Schema dialect?
A schema without an explicit dialect leaves tools to choose their own assumptions. Add the appropriate $schema URI, then configure your validator for that same draft. Keywords and behaviors change between drafts, so a document interpreted as Draft 7 may not behave like the Draft 2020-12 contract its author intended.
Start with the declaration at the schema root:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object"
}
The declaration is not decoration. It identifies the vocabulary and semantics the schema expects. This matters when a codebase contains schemas written at different times or when an API description embeds a particular dialect.
The JSON Schema specification page links the published drafts and explains their status. Check the implementation's documentation too, because recognizing a dialect URI and fully implementing its vocabularies are separate questions. In OpenAPI projects, verify the OpenAPI version as well; our JSON Schema and OpenAPI comparison explains why a 3.0 Schema Object should not be treated as an unrestricted Draft 2020-12 schema.
Record the selected draft beside the validator dependency in your architecture notes. A useful compatibility test loads the production schema with the production options and fails the build if compilation produces warnings or unknown-keyword errors. That catches accidental validator downgrades before payload behavior changes.
For background, Wikipedia's JSON article describes the data format itself. JSON Schema adds a separate validation language on top of JSON; syntactically valid JSON does not automatically satisfy a schema.
2. Are you assuming properties are required just because you described them?
The properties keyword does not require those properties to exist. It applies a subschema when a matching name is present. An object can therefore pass with every described field missing. Put mandatory names in the object's required array, and test an empty object plus payloads missing one field at a time.
This schema describes email, but it does not demand email:
{
"type": "object",
"properties": {
"email": { "type": "string" }
}
}
According to the official JSON Schema object reference, properties are optional by default. That makes sense for partial objects and patch documents, but it surprises developers who read properties as a list of fields the payload must contain.
The repaired version expresses presence separately:
{
"type": "object",
"properties": {
"email": { "type": "string" }
},
"required": ["email"]
}
Keep required on the same object level as the property names it refers to. A nested address needs its own required list inside the address subschema. Requiring address at the root does not require address.city.
Create one negative test per mandatory field. If an order requires id, items, and currency, test three payloads, each omitting a different field. One test that removes all three can pass for the wrong reason and fails to prove each rule independently.
3. Does required incorrectly stand in for non-null or non-empty values?
The required keyword checks whether an object contains a named property; it does not make that property's value meaningful. An empty string, empty array, zero, or null may still pass depending on the property's subschema. Combine presence with the exact type and value constraints your application actually needs.
Suppose a display name must contain at least one Unicode character:
{
"type": "object",
"properties": {
"displayName": {
"type": "string",
"minLength": 1
}
},
"required": ["displayName"]
}
Without minLength, the empty string is still a string. For arrays, use minItems; for objects, use minProperties; for numbers, use the relevant inclusive or exclusive bound. Avoid adding constraints merely because they look stricter. A zero quantity may be invalid for a purchase but valid for an inventory adjustment.
Null is its own JSON value. If a property must be a string, "type": "string" rejects null. If null is permitted, express that choice using the syntax supported by your dialect, such as "type": ["string", "null"] in Draft 2020-12. Our null, missing key, and undefined guide explains why presence and nullability should remain separate decisions.
Test missing, null, empty, and boundary values individually. These cases may look similar in a user interface, but a validator evaluates different rules for each one.
4. Are unknown properties passing because objects are open by default?
JSON Schema object definitions allow unknown properties unless another keyword restricts them. A misspelled field can therefore pass while the intended field remains absent or unused. Decide whether the contract is open or closed, then apply additionalProperties or unevaluatedProperties at the correct object level and test extension behavior deliberately.
A closed object can reject a typo such as curreny:
{
"type": "object",
"properties": {
"currency": { "enum": ["USD", "EUR", "INR"] }
},
"required": ["currency"],
"additionalProperties": false
}
The official object reference confirms that extra properties are allowed by default. Closing every object is not automatically better: extension data, vendor fields, and forward-compatible clients may need room to evolve. Choose based on the API's compatibility policy.
Remember that the keyword acts at one object level. Placing additionalProperties: false at the root does not close an object nested under customer. Conversely, putting it inside a reusable subschema can stop consumers from extending that schema through composition.
For controlled extensions, additionalProperties can itself be a schema rather than a boolean. You might permit extra keys only when their values are strings. For prefix-based fields, combine anchored patternProperties expressions with a policy for everything unmatched. Test near-miss names because JSON Schema regular expressions are not automatically anchored.
5. Are you treating format as guaranteed validation?
The format keyword does not guarantee rejection in every JSON Schema implementation. Draft 2020-12 separates format annotation from format assertion, and validators expose different defaults or plugins. Confirm the production configuration for values such as email, URI, UUID, and date-time instead of assuming that adding format activates a universal checker.
The Draft 2020-12 validation specification says implementations must not validate formats by default unless assertion behavior is enabled through the applicable vocabulary and configuration. A validator may collect format as useful metadata while accepting a malformed value.
This distinction also prevents overclaiming. An email format checker can determine whether a string resembles an address under its implemented rules. It cannot prove ownership, delivery, or mailbox existence. A date-time checker can validate syntax without confirming that the timestamp makes sense in your business workflow.
If your JavaScript service uses Ajv, verify the exact version, format package, and options rather than copying an example from another project. Put an intentionally invalid email and timestamp in your test suite. The test should fail under the same initialization code used by production.
When date-time handling matters, review our JSON date-format guide. It separates JSON string syntax from RFC 3339 conventions and application-level time decisions.
6. Can oneOf match more than one branch?
The oneOf keyword succeeds only when exactly one branch validates. If alternatives overlap, data that looks valid may fail because two branches match. Design branches with distinguishing required properties or const values, test each branch independently, and include a payload that would match both to prove the alternatives are actually exclusive.
Consider two event shapes that both accept an object with an optional string. Without a required discriminator, an empty object might satisfy both. Adding a stable event type makes the branches explicit:
{
"oneOf": [
{
"properties": {
"type": { "const": "created" },
"record": { "type": "object" }
},
"required": ["type", "record"]
},
{
"properties": {
"type": { "const": "deleted" },
"recordId": { "type": "string" }
},
"required": ["type", "recordId"]
}
]
}
Use anyOf when overlap is acceptable and at least one branch must pass. Use allOf when every listed subschema must pass. These names are short, but they express different Boolean conditions; switching keywords until a sample passes is not contract design.
The official conditional-validation guide covers implications and if/then/else patterns. For production errors, preserve each branch's failure details internally while returning a safe, concise API error response using a consistent JSON error format.
7. Did additionalProperties false break an allOf extension?
The additionalProperties keyword only recognizes properties evaluated by properties and patternProperties in the same subschema. A closed base schema inside allOf can reject fields added by another branch. In Draft 2020-12, unevaluatedProperties may better express closure after composition, provided your validator supports the selected dialect.
This is one of the least obvious schema failures because each fragment appears reasonable in isolation. A base address closes itself, an extension adds type, and the composed instance fails because the base sees type as additional.
The JSON Schema object documentation includes an extending closed schemas example showing this exact scope issue. One workaround is to redeclare the known properties where additionalProperties: false applies. Draft 2019-09 and later also provide unevaluatedProperties, whose behavior considers annotations collected through applicable subschemas.
Do not mechanically replace every occurrence. Composition behavior depends on the active dialect and the validator's implementation. Build a small fixture containing a valid base instance, a valid extended instance, an unknown-field instance, and a missing-field instance. Run it in every language that consumes the shared schema.
Reusable definitions remain valuable. The JSON Schema primer covers $defs and $ref; the lesson here is to decide where a reusable component should remain extensible and where the final assembled contract should close.
8. Are you expecting default to modify the data?
The JSON Schema default keyword supplies an annotation, not a universal instruction to insert a missing value. Some validators can mutate data through optional, implementation-specific settings, while others leave it untouched. Keep validation and transformation conceptually separate, document any mutation mode, and test what downstream code receives after validation.
This schema communicates a suggested default without guaranteeing a changed instance:
{
"type": "object",
"properties": {
"pageSize": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25
}
}
}
Ajv documents useDefaults, removeAdditional, and coercion under modifying data during validation. Those options are useful in some systems, but Ajv also warns that mutation is nonstandard and can make results depend on evaluation order. Code written for another validator should not assume the same behavior.
A clean pipeline often has three explicit steps: parse JSON, validate the original value, then apply documented defaults or normalization in application code. If you intentionally transform before validation, validate the transformed result too and keep diagnostics clear about which representation failed.
Test whether the original object changes. Test a supplied valid value, a missing value, and an invalid value. Never let a default hide an invalid field: pageSize: 0 should fail the minimum rather than silently becoming 25.
These eight mistakes share one fix: treat the schema, dialect, validator, and options as one executable contract. Store representative passing and failing payloads beside it, run them in continuous integration, and repeat the tests when any of those four parts changes.
For partial updates, these eight JSON Merge Patch API tests check that a valid patch also produces a valid complete resource before persistence.
For string constraints, run the JSON Schema regex deployment checks to test escaping, boundaries, Unicode length, engine compatibility, and execution cost separately.
For modular contracts, the JSON Schema reference release checks cover resolution failures that ordinary passing payloads can miss, including wrong bases, moved definitions, and missing deployment resources.