How to Generate JSON Schema from JSON: Step-by-Step Guide
I once inherited an API with 47 endpoints and zero documentation. The previous developer had left, and the only thing we had was a Postman collection with example responses.
My first task was to write a JSON Schema for each endpoint. I started manually — counting fields, checking types, figuring out which fields were required. After three hours and two endpoints, I gave up and automated it.
This article covers how JSON Schema generation works, the decisions a generator makes, and the gotchas I discovered along the way.
What JSON Schema Generation Actually Does
A JSON Schema generator takes a sample JSON document and infers its structure:
{
"name": "Alice",
"age": 30,
"email": "alice@example.com"
}
Becomes:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "integer" },
"email": { "type": "string", "format": "email" }
},
"required": ["name", "age", "email"]
}
On Stack Overflow, a question about programmatic JSON Schema generation has over 55,000 views. Most answers point to existing tools, but the top answer (400+ upvotes) explains the algorithm: type inference + required field detection + format inference.
Type Inference: The Easy Part
The most straightforward part is inferring types from values:
function inferType(value) {
if (value === null) return { type: "null" };
if (Array.isArray(value)) {
// Infer from array items
const itemTypes = value.map(inferType);
const merged = mergeTypes(itemTypes);
return { type: "array", items: merged };
}
switch (typeof value) {
case "string":
// Try to detect common formats
if (/^\d{4}-\d{2}-\d{2}/.test(value)) {
return { type: "string", format: "date-time" };
}
if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
return { type: "string", format: "email" };
}
if (/^https?:\/\//.test(value)) {
return { type: "string", format: "uri" };
}
return { type: "string" };
case "number":
return Number.isInteger(value) ? { type: "integer" } : { type: "number" };
case "boolean":
return { type: "boolean" };
case "object":
return {
type: "object",
properties: Object.fromEntries(
Object.entries(value).map(([k, v]) => [k, inferType(v)])
),
required: Object.keys(value)
};
default:
return {};
}
}
This works for a single sample. But here is the problem: a single sample tells you nothing about which fields are truly optional.
The Single-Sample Problem
Generating a schema from one JSON sample treats every present field as required. I made this mistake and shipped a schema that rejected valid API responses.
// Sample 1 (what I tested with):
{
"id": 1,
"name": "Alice",
"email": "alice@example.com",
"phone": "+1-555-0123"
}
// Generated schema marks all four fields as required
// Sample 2 (real production data):
{
"id": 2,
"name": "Bob"
// email and phone are missing — schema rejects it!
}
The fix: generate schemas from multiple samples, or explicitly mark fields as optional unless you know they are always present.
// Better approach: collect multiple samples, then infer optionality
function generateSchemaFromSamples(samples) {
// Track which fields appear in every sample
const fieldPresence = {};
samples.forEach(sample => {
Object.keys(sample).forEach(key => {
fieldPresence[key] = (fieldPresence[key] || 0) + 1;
});
});
const required = Object.entries(fieldPresence)
.filter(([, count]) => count === samples.length)
.map(([key]) => key);
// Build the schema with proper required/optional distinction
return {
type: "object",
properties: buildProperties(samples[0]), // Use first sample for shape
required
};
}
A discussion on r/programming about this exact issue has over 200 comments, with many developers sharing stories of schemas that were too strict because they were based on too few samples.
Nested Objects and $ref
When your JSON has nested objects, the generated schema gets complex quickly:
{
"user": {
"profile": {
"avatar": "https://example.com/avatar.png",
"preferences": {
"theme": "dark",
"notifications": true
}
}
}
}
A naive generator creates deeply nested schemas that are hard to read. A good generator creates $ref references:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"user": { "$ref": "#/definitions/User" }
},
"required": ["user"],
"definitions": {
"User": {
"type": "object",
"properties": {
"profile": { "$ref": "#/definitions/Profile" }
},
"required": ["profile"]
},
"Profile": {
"type": "object",
"properties": {
"avatar": { "type": "string", "format": "uri" },
"preferences": { "$ref": "#/definitions/Preferences" }
},
"required": ["avatar", "preferences"]
},
"Preferences": {
"type": "object",
"properties": {
"theme": { "type": "string" },
"notifications": { "type": "boolean" }
},
"required": ["theme", "notifications"]
}
}
}
Format Detection: The Hidden Value
Beyond basic types, schema generators can detect common formats:
const formatDetectors = [
{ regex: /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/, format: "date-time" },
{ regex: /^\d{4}-\d{2}-\d{2}$/, format: "date" },
{ regex: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, format: "email" },
{ regex: /^https?:\/\/.+/, format: "uri" },
{ regex: /^[^-][A-Za-z0-9-]+(\.[A-Za-z]{2,})+$/, format: "hostname" },
{ regex: /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, format: "ipv4" },
];
function detectFormat(value) {
if (typeof value !== "string") return null;
for (const detector of formatDetectors) {
if (detector.regex.test(value)) return detector.format;
}
return null;
}
On Stack Overflow, a question about format detection has 25,000 views, and the consensus is that format detection catches about 80% of cases but has a 5-10% false positive rate — especially with date-time vs date detection.
Related Searches
- json schema generator from json
- generate json schema online
- json schema from sample data
- automatic json schema generation
- json schema type inference
- json schema format detection
- json schema nested objects
- json schema code generation
- openapi schema from json
- json validation with generated schema
Frequently Asked Questions
Can you generate a perfect JSON Schema from a single JSON sample?
No. A single sample cannot tell you which fields are optional, what the minimum/maximum values are, or what the full range of enum values should be. Multiple samples produce significantly better schemas. Treat generated schemas as a starting point, not a final product.
How does JSON Schema generation handle arrays?
The generator inspects the first few items in the array to infer the item type. If the array contains mixed types (numbers and strings), the schema uses oneOf or anyOf. For empty arrays, the generator cannot infer the item type and defaults to {} (accepts anything).
Should generated schemas include format constraints?
Yes, if you can detect them reliably. Common formats like email, URI, and date-time are relatively safe. Overly aggressive format detection (like misidentifying a version string "1.2.3" as an IP address) causes false validation failures. I recommend manual review of format annotations.
How do I validate a generated schema against real data?
Run your generated schema against a representative set of real API responses. Expect some failures — generated schemas are often too strict (marking optional fields as required) or too loose (not catching format violations). Iterate based on the validation errors.
What is the difference between generated JSON Schema and OpenAPI?
JSON Schema describes the shape of JSON data. OpenAPI describes an entire API (endpoints, parameters, responses) and uses JSON Schema (or a subset) for request/response body descriptions. A generated JSON Schema can be embedded in an OpenAPI spec's components/schemas section.
Final Thoughts
JSON Schema generation from sample data is a time-saver, not a magic wand. The most useful generated schemas come from multiple samples, include format annotations, and use $ref for nested structures. Always test generated schemas against production data before using them for validation.
The most important lesson I learned: treat generated schemas as a draft. Review required fields, check format annotations, and refine based on real validation results. A generated schema that has never been tested against real data is just a guess.
Use the JSON Schema Generator on DevFormatters to generate a schema from your JSON, then the JSON Schema Validator to test it against your actual data.