Turn a sample document into a JSON Schema you can validate against. The generator reads the shape, tags common string formats such as email and date-time, and marks every key that appears in all sample objects as required. You then add the ranges, enums and lengths by hand, since a sample carries none of them.
{
"id": 1,
"name": "Ana",
"orders": [
{ "order_id": 1001, "note": "gift" },
{ "order_id": 1002 }
]
} {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "User",
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"orders": {
"type": "array",
"items": {
"type": "object",
"properties": {
"order_id": {
"type": "integer"
},
"note": {
"type": "string"
}
},
"required": [
"order_id"
]
}
}
},
"required": [
"id",
"name",
"orders"
]
} Reliable. Objects, arrays, nested shapes and the type of every leaf come straight from the data.
A guess. The generator marks a key required when it appears in every sample object. If your sample happened to include an optional field every time, the schema now insists on it, so review this list first.
Matched by pattern: date-time, date, time, email, uuid, uri and ipv4. However, a string that looks like a date is not always meant to be one. For example, a version field of 2024-01-15 gets format: date.
Ranges, enums, string lengths, mutually exclusive fields and defaults. The sample holds none of this, so the schema has none of it either.
Use 2020-12 unless a dependency requires draft-07.
OpenAPI 3.1 adopted 2020-12, so a 3.1 spec can reference the
output directly. Ajv supports it through its
Ajv2020 class (the default Ajv export
is draft-07). Draft-07 stays in the dropdown because many
existing projects are pinned to it, and migrating only for the
new draft rarely pays off.
OpenAPI 3.0 fits neither draft cleanly. It uses its own dialect
with nullable: true instead of a type union, and
treats exclusiveMinimum as a boolean. Generate
here, then adjust those two by hand.
// npm i ajv ajv-formats
const Ajv = require("ajv");
const addFormats = require("ajv-formats");
const ajv = new Ajv({ allErrors: true });
addFormats(ajv); // without this, "format": "email" is ignored entirely
const validate = ajv.compile(require("./schema.json"));
const data = require("./payload.json");
if (!validate(data)) {
for (const e of validate.errors) {
console.error(`${e.instancePath || "/"} ${e.message}`);
}
} Without ajv-formats, Ajv treats format as an annotation, so a schema that appears to check email addresses accepts any string. For a 2020-12 schema, import Ajv from "ajv/dist/2020"; the default Ajv class targets draft-07 and rejects the 2020-12 $schema URI.
# pip install jsonschema
import json
from jsonschema import Draft202012Validator
schema = json.load(open("schema.json"))
data = json.load(open("payload.json"))
validator = Draft202012Validator(schema)
for error in sorted(validator.iter_errors(data), key=lambda e: e.path):
path = "/".join(str(p) for p in error.absolute_path) or "(root)"
print(f"{path}: {error.message}") # Validate every fixture against the schema on each push
npx ajv-cli validate -s schema.json -d "fixtures/*.json" --all-errors
# Check the schema itself is a valid schema
npx ajv-cli compile -s schema.json
# Useful as a contract test: if the API team changes a response,
# the fixture stops validating and the build tells you. A generated schema describes one sample. It has no ranges, enums or string lengths, and no rule that says exactly one of two fields must be present. Those constraints catch most real bugs, and you add all of them by hand. Use the output for the repetitive type and property declarations, then spend your time on the rules.
This tool follows the specifications below. Read them when you need the exact normative wording, for example to settle an edge case between two implementations.
Draft 2020-12 by default, which is the dialect OpenAPI 3.1 uses and the one Ajv supports through its Ajv2020 class. Draft-07 is in the dropdown because many existing projects are pinned to it.
A key present in every sample object is required, and a key present in only some is not. That rule guesses from one sample, so read the required array before you ship the schema. It is the field most likely to be wrong.
Yes, by pattern: date-time, date, time, email, uuid, uri and ipv4. For example, 2026-09-10T08:15:00Z gets format: date-time. Untick Infer formats if you would rather start from plain strings and add each constraint yourself.
Yes for OpenAPI 3.1, which uses draft 2020-12, so the output drops in directly. OpenAPI 3.0 uses an older dialect that differs mainly on nullable and exclusiveMinimum, so adjust those two by hand.
Treat it as a starting point. It describes the shape of the sample you gave it and nothing about your intent, so it has no ranges, enums, string lengths or cross-field rules. Generate it, then add those constraints by hand where correctness matters.
If you want types instead of a contract, the same sample produces a TypeScript interface, a Go struct or a Python model. To see what changed between two payloads, use the JSON diff.