SchemaSafe Blog

Common JSON Schema authoring mistakes

JSON Schema, currently Draft 2020-12, is the standard way to describe and validate JSON shape. Most broken schemas are not syntax errors; they are permissive mistakes that pass invalid data. The four that surface most in code review are a missing required array, an open additionalProperties, misused type unions, and a $ref that points nowhere.

Forgetting the required array

When you add a property under properties you are only describing its shape, not demanding its presence. If the required array omits that key, an instance without the field validates successfully. This is a silent pass because the validator treats the property as optional. I have seen teams ship APIs where a critical identifier was missing from required , leading to downstream null‑pointer errors that only appeared in production.

For example, a user schema that expects an id should contain:

{ "type": "object", "properties": { "id": { "type": "string" }, "name": { "type": "string" } }, "required": ["id"] } Without the required line, an object with only {"name":"Ada"} would pass, even though the API later treats id as mandatory.

Leaving additionalProperties open

The default value of additionalProperties is true . This lets any key not defined in properties slip through validation. A typo such as "user_name" instead of "username" then becomes a silently accepted field, polluting the data model and making later migrations harder. I have seen teams spend weeks cleaning up stray keys that entered the database because the schema never rejected them.

Here is a strict version of a product schema:

{ "type": "object", "properties": { "sku": { "type": "string" }, "price": { "type": "number", "minimum": 0 } }, "required": ["sku","price"], "additionalProperties": false } With this schema, an instance containing {"sku":"ABC","price":10,"extra":1} fails validation, catching the typo early.

Misusing type unions

The type keyword expects either a string or an array of strings. Writing "type": "string, number" is invalid JSON Schema; the parser treats the whole value as a single unknown type, which many validators silently ignore, allowing any value to pass. Another common slip is omitting the type entirely for an optional field, which makes the validator skip type checking for that key.

Example of a nullable date field:

{ "type": "object", "properties": { "issuedAt": { "type": ["string","null"], "format": "date-time" } }, "required": ["issuedAt"] } Here null is allowed, so {"issuedAt":null} passes, while {"issuedAt":"not-a-date"} fails.

$ref traps and version drift

A $ref that resolves to a missing location causes the whole schema to fail at load time, which is noisy and easy to spot. The quieter problem is a reference that resolves but points to the wrong definition because the draft version changed or the folder structure shifted. In Draft 2020-12 the keyword for reusable blocks moved from definitions to $defs . If you keep the old key, the schema still validates (the older keyword is ignored) but you lose the ability to reuse definitions, leading to duplicated code and drift.

Example of a correct reference:

{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$defs": { "address": { "type": "object", "properties": { "street": { "type": "string" }, "city": { "type": "string" } }, "required": ["street","city"] } }, "type": "object", "properties": { "billing": { "$ref": "#/$defs/address" }, "shipping": { "$ref": "#/$defs/address" } }, "required": ["billing","shipping"] } If you mistakenly wrote "$ref": "#/definitions/address" the reference would resolve to undefined and the validator would throw an error, alerting you to the mismatch.

Validate with samples before publishing

The cheapest confidence check is to run a handful of representative JSON instances through the validator before you treat the schema as a contract. A good test suite contains at least one valid example that matches the intended use and one invalid example that deliberately breaks each rule you care about. If the schema accepts the good sample and rejects the bad one, you have evidence that it is not overly permissive. I have seen teams skip this step and later discover that a missing required allowed orders without product IDs to slip through, causing fulfillment errors.

const Ajv = require('ajv'); const ajv = new Ajv({allErrors: true}); const schema = require('./user-schema.json'); const good = { "id": "123", "name": "Ada" }; const bad = { "name": "Ada" }; // missing id const validate = ajv.compile(schema); console.log('Good sample:', validate(good) ? 'pass' : 'fail'); if (!validate(good)) console.error(validate.errors); console.log('Bad sample:', validate(bad) ? 'pass' : 'fail'); if (validate(bad)) console.error('Unexpectedly accepted bad sample'); Running this in CI ensures that a permissive mistake never reaches production.

What this is NOT

This guide does not promise that following these points will make your schema immune to every possible mistake. JSON Schema is a flexible language, and new pitfalls can appear as you extend it with custom keywords or combine it with other tools like OpenAPI. I am not claiming that the four errors listed are the only ones that matter; they are simply the ones that appear most often in the code reviews I have seen. The advice here is based on my experience maintaining SchemaSafe and on publicly available specifications; it is not a substitute for reading the official specification or for running your own validation tests.

References

https://ajv.js.org/json-schema.html

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse

https://json-schema.org

https://json-schema.org/draft/2020-12/json-schema-core.html

FAQ

Declaring a property in properties does not make it required. You must list it in the required array. The array is a sibling of properties , not nested inside it.

It defaults to true, so unknown keys pass. Set additionalProperties: false where strictness matters, or constrain it with a schema for extension fields. Open schemas let typos through silently.

Omit it from required and include "null" in its type array, for example ["string", "null"] . Writing a comma-separated type string is invalid and can void the constraint.

Under $defs . The older definitions key still works but marks an old draft. Always pin $schema so the validator applies the draft you intended.

Try the product: SchemaSafe

2026-09-16 · primary sources only · no fabricated traffic metrics

Related articles

JSON Schema validation without an LLM (2026)

Explore the full AI micro-SaaS topic matrix →

Why does my schema accept missing fields?

Declaring a property in properties does not make it required. You must list it in the required array. The array is a sibling of properties , not nested inside it.

What does additionalProperties do by default?

It defaults to true, so unknown keys pass. Set additionalProperties: false where strictness matters, or constrain it with a schema for extension fields. Open schemas let typos through silently.

How do I mark a field optional in JSON Schema?

Omit it from required and include \

Where do reusable definitions go in Draft 2020-12?

Under $defs . The older definitions key still works but marks an old draft. Always pin $schema so the validator applies the draft you intended.

Decision-support only. Not a substitute for formal schema review or certified audits.

← Back to blog · Read full English article

Was this result helpful?Feedback