How to create a JSON Schema from JSON
· 5 min read · By Amir Badi
You have JSON from an API or a config file and you need a schema for it: to validate requests, to document an API or to catch a broken file before it reaches production. Writing one by hand is slow. Generating one from an example takes a second, but the result is only a starting point.
The quickest route: paste your JSON into the JSON Schema Generator, copy the schema, then tighten the few rules a generator can't guess. Below is a worked example with the generator's real output, the changes I'd make, and what the JSON Schema Validator reports afterwards.
What a JSON Schema is
A JSON Schema is a JSON document that describes other JSON: which fields exist, their types, which ones are required and what values are allowed. A validator reads both and lists every place where the data breaks a rule.
{
"type": "object",
"properties": { "id": { "type": "integer" } },
"required": ["id"]
}
This one says: the data is an object, and it must have an id that's a whole number.
Step 1: generate a schema from example JSON
Give the generator more than one record. From a single object it can't know which fields are optional, so it marks every key as required. Here are two users from an API response:
{
"users": [
{ "id": 1, "name": "Ada Lovelace", "email": "ada@example.com", "role": "admin",
"signedUpAt": "2026-03-14T09:30:00Z", "tags": ["beta"], "manager": null },
{ "id": 2, "name": "Linus", "email": "linus@example.com", "role": "editor",
"signedUpAt": "2026-05-02T16:05:00Z", "tags": [], "manager": 1, "phone": "+44 20 7946 0000" }
]
}
The generator's output, unedited:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"users": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"email": { "type": "string" },
"role": { "type": "string" },
"signedUpAt": { "type": "string" },
"tags": { "type": "array", "items": { "type": "string" } },
"manager": { "type": ["null", "integer"] },
"phone": { "type": "string" }
},
"required": ["id", "name", "email", "role", "signedUpAt", "tags", "manager"]
}
}
},
"required": ["users"]
}
It got the structure right:
phoneis optional, because only one user has it.managercan benullor a whole number, because the examples had one of each.tagsis a list of strings, even though one user's list was empty.
Step 2: decide what the generator couldn't know
Now feed it a record that's clearly wrong:
{ "users": [ { "id": 0, "name": "Grace", "email": "grace.example.com", "role": "owner",
"signedUpAt": "yesterday", "tags": [], "manager": null, "nickname": "G" } ] }
The generated schema accepts it: 0 errors. Every field has the right type, and that's all a generator can check. It saw two emails, not a rule about emails. It saw "admin" and "editor", not the list of allowed roles. The rules come from you:
| Rule you know | Keyword to add |
|---|---|
| ids start at 1 | "minimum": 1 |
email is an email address |
"format": "email" |
| a role is admin, editor or viewer | "enum": ["admin", "editor", "viewer"] |
signedUpAt is a timestamp |
"format": "date-time" |
| no other fields are allowed | "additionalProperties": false |
Step 3: make the schema strict
Change those fields in items:
"id": { "type": "integer", "minimum": 1 },
"email": { "type": "string", "format": "email" },
"role": { "enum": ["admin", "editor", "viewer"] },
"signedUpAt": { "type": "string", "format": "date-time" },
Then add "additionalProperties": false beside "required". Paste the schema and the bad record into the JSON Schema Validator, and it lists five errors, each with the path of the value:
$.users[0].id 0 is less than 1.
$.users[0].email String does not match format "email".
$.users[0].role Instance does not match any of ["admin","editor","viewer"].
$.users[0].signedUpAt String does not match format "date-time".
$.users[0].nickname Property "nickname" is not allowed.
The two good users still pass with no errors.
Be careful with additionalProperties: false. It's right for data you produce, like a config file or a request body, where an unknown key is usually a typo. For responses from an API you don't control, leave it out, or a new field the provider adds will break your validation.
When records have different shapes
Lists often hold more than one kind of object. Two payment methods:
[ { "type": "card", "last4": "4242" }, { "type": "bank", "iban": "DE89" } ]
A generator merges them into one object type where only type is required and last4 and iban are both optional. That schema accepts a card with an iban and no last4. To say what you mean, split it with oneOf and const:
{
"type": "array",
"items": {
"oneOf": [
{ "type": "object", "properties": { "type": { "const": "card" }, "last4": { "type": "string", "pattern": "^[0-9]{4}$" } }, "required": ["type", "last4"] },
{ "type": "object", "properties": { "type": { "const": "bank" }, "iban": { "type": "string" } }, "required": ["type", "iban"] }
]
}
}
Now { "type": "card", "iban": "DE89" } fails: it doesn't match the card shape (no last4) or the bank shape (the type isn't "bank").
Going the other way: JSON from a schema
Sometimes you have the schema and need test data. In the GigaJSON app, open your document, go to Types → Validate, paste the schema and choose Apply schema. Then ⋯ → Generate sample data… creates records that follow it: values picked from enum, numbers within minimum and maximum, and realistic emails and timestamps for format: "email" and "date-time". The data goes to a new document by default, so your original stays as it was.
If you need TypeScript types rather than a schema, JSON to TypeScript reads the same example JSON and writes interfaces, with optional fields marked ?.
A short checklist
- Generate from several real records, not one.
- Check
required: is every key in it really mandatory? - Add
format,enum,minimum,maximumandpatternwhere you know the rule. - Add
additionalProperties: falseonly for data you control. - Split mixed lists with
oneOf. - Test with one good record and one bad one before you rely on it.