JSON Schema: मूल बातों से प्रोडक्शन तक
मुख्य बात: Keywords बहुत हैं, पर रोज़ केवल एक दर्जन इस्तेमाल होते हैं। type/properties/required/$ref सही कर लें, error messages अच्छे संभालें, और आप असली मामलों का अस्सी प्रतिशत कवर कर लेंगे।

सारांश: पहले $schema को 2020-12 पर पिन करें, फिर $defs + $ref से साझा संरचनाएँ निकालें; oneOf केवल तब इस्तेमाल करें जब ठीक एक branch का मेल ज़रूरी हो, और बाकी हर जगह discriminators या anyOf को प्राथमिकता दें।
Schema दस्तावेज़ का परिशिष्ट नहीं; वह ऐसी संपत्ति है जिसे चलाया, जाँचा और जनरेट किया जा सकता है।
1. पहले version साफ़ करें
| Version | $schema मान |
स्थिति | टिप्पणियाँ |
|---|---|---|---|
| draft-04 | http://json-schema.org/draft-04/schema# |
पुराना | Legacy प्रोजेक्ट्स और कुछ OpenAPI 2.0 tooling |
| draft-07 | http://json-schema.org/draft-07/schema# |
व्यापक रूप से उपयोग | OpenAPI 3.0 का de facto आधार |
| 2019-09 | https://json-schema.org/draft/2019-09/schema |
उपलब्ध | $defs और annotation vocabulary पेश किए |
| 2020-12 | https://json-schema.org/draft/2020-12/schema |
वर्तमान में अनुशंसित | prefixItems पेश किया, items की semantics बदलीं |
नए प्रोजेक्ट्स के लिए 2020-12 इस्तेमाल करें। ध्यान दें कि 2020-12 में items केवल "शेष elements" को सीमित करता है — tuple-शैली declarations prefixItems में चले गए।
draft-07 से migrate करते समय यह सबसे आम जाल है।
2. एक न्यूनतम उपयोगी schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://bbdrug.com/schemas/user.json",
"type": "object",
"required": ["id", "name"],
"properties": {
"id": { "type": "string", "minLength": 1 },
"name": { "type": "string", "minLength": 1, "maxLength": 64 },
"age": { "type": "integer", "minimum": 0, "maximum": 150 },
"tags": { "type": "array", "items": { "type": "string" }, "uniqueItems": true }
},
"additionalProperties": false
}
यह schema चार सबसे आम चिंताएँ कवर करता है: type बाधाएँ, required फ़ील्ड, range सीमाएँ और array element बाधाएँ।
3. Keyword नक्शा
3.1 सामान्य
| Keyword | उद्देश्य | किस पर ध्यान दें |
|---|---|---|
type |
Type; array हो सकता है | "type": ["string", "null"] का अर्थ nullable |
enum |
Enumerated मान | मान अद्वितीय होने चाहिए |
const |
एकल-मान बाधा | version number जैसे निश्चित मानों के लिए अच्छा |
$ref |
किसी अन्य schema का संदर्भ | पूरे schema का संदर्भ देता है और संदर्भ देने वाले object के sibling keywords के साथ merge नहीं होता |
3.2 संख्याएँ
multipleOf, maximum, exclusiveMaximum, minimum, exclusiveMinimum.
ध्यान दें कि draft-04 में exclusiveMaximum एक boolean था (जो maximum के साथ मिलकर इस्तेमाल होता था); draft-07 से ही यह स्वतंत्र संख्यात्मक keyword बना —
जिससे "पुराने दस्तावेज़ों से नकल किए गए बहुत से schemas" strict mode में विफल होते हैं।
3.3 स्ट्रिंग्स
minLength, maxLength, pattern (एक ECMA-262 regular expression), और format (एक annotation)।
minLength Unicode code points गिनता है, UTF-16 units या bytes नहीं। JavaScript के string.length से validate करने पर इससे मतभेद होगा।
3.4 Arrays
| Keyword | उद्देश्य |
|---|---|
prefixItems |
2020-12 tuple-शैली बाधा, index के अनुसार items एक-एक करके मिलाती है |
items |
शेष elements पर एकसमान बाधा (2020-12) |
contains + minContains/maxContains |
कम-से-कम/अधिक-से-अधिक कितने मेल खाते items मौजूद हों |
uniqueItems |
Elements सब अलग होने चाहिए (deep comparison; बड़े arrays पर बचें) |
minItems / maxItems |
लंबाई बाधाएँ |
3.5 Objects
properties, required, patternProperties, additionalProperties, propertyNames,
minProperties, maxProperties, dependentRequired, dependentSchemas.
तीन उच्च-आवृत्ति भ्रांतियाँ:
requiredकेवल key का मौजूद होना चाहता है:{"name": ""}मान्य है। खाली स्ट्रिंग अस्वीकार करने के लिएminLength: 1जोड़ना होगा;additionalProperties: falsecompatibility समस्याएँ बढ़ा देता है: client का एक अतिरिक्त फ़ील्ड पूरा payload विफल कर देता है, इसलिए API विकसित करने पर हर caller को एक साथ upgrade करना पड़ता है;propertiesundeclared keys को सीमित नहीं करता — डिफ़ॉल्ट रूप से वे अनुमत हैं।
3.6 Logical composition
{
"oneOf": [
{ "type": "object", "required": ["cardNumber"], "properties": { "cardNumber": { "type": "string" } } },
{ "type": "object", "required": ["accountId"], "properties": { "accountId": { "type": "string" } } }
]
}
| Keyword | Semantics | विशिष्ट विफलता |
|---|---|---|
allOf |
सभी को संतुष्ट होना चाहिए | कई branches एक ही फ़ील्ड पर परस्पर विरोधी बाधाएँ लगाती हैं |
anyOf |
कम से कम एक को संतुष्ट होना चाहिए | Error message को विशिष्ट branch तक पहुँचाना कठिन |
oneOf |
ठीक एक को संतुष्ट होना चाहिए | डेटा दो branches से मेल खाता है और विफल हो जाता है, भले ही वह "स्पष्ट रूप से सही दिखता है" |
not |
संतुष्ट नहीं होना चाहिए | कम इस्तेमाल, और सबसे कम मित्रवत error messages |
oneOf वह keyword है जो सबसे अधिक "मान्य डेटा जो validation में विफल होता है" पैदा करता है। अगर branch बाधाएँ overlap करती हैं (उदाहरण के लिए दोनों अतिरिक्त फ़ील्ड की अनुमति देती हैं),
तो एक record कई branches से मेल खाता है और oneOf सीधे विफल हो जाता है। उस स्थिति में anyOf पर switch करें, या branches के अंदर const के साथ discriminator फ़ील्ड इस्तेमाल करें।
{
"type": "object",
"required": ["kind"],
"properties": { "kind": { "enum": ["text", "image"] } },
"if": { "properties": { "kind": { "const": "image" } } },
"then": { "required": ["url", "width", "height"] },
"else": { "required": ["content"] }
}
Discriminator शैली oneOf से अधिक पठनीय है, और बेहतर errors देती है।
4. बाँटना और दोबारा उपयोग
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://bbdrug.com/schemas/order.json",
"$defs": {
"money": { "type": "string", "pattern": "^-?\\d+\\.\\d{2}$" },
"address": {
"type": "object",
"required": ["city"],
"properties": { "city": { "type": "string" }, "detail": { "type": "string" } }
}
},
"type": "object",
"required": ["total", "shipTo"],
"properties": {
"total": { "$ref": "#/$defs/money" },
"shipTo": { "$ref": "#/$defs/address" }
}
}
$defs2019-09 में पेश किया गया रूप है; draft-07definitionsइस्तेमाल करता है;- Cross-file references सापेक्ष
$ids इस्तेमाल करते हैं, लेकिन प्रोडक्शन में runtime पर remote schemas fetch करने पर निर्भर न रहें — इन्हें स्थिर फ़ाइलों के रूप में bundle करें; - आप संदर्भ के साथ-साथ बाधाएँ नहीं जोड़ सकते (sibling keywords के साथ
$refका व्यवहार version के अनुसार बदलता है)। Composition चाहिए तोallOfइस्तेमाल करें।
5. Ajv से runtime validation
import Ajv2020 from 'ajv/dist/2020';
import addFormats from 'ajv-formats';
import orderSchema from './order.json' with { type: 'json' };
const ajv = new Ajv2020({
allErrors: true,
strict: true,
allowUnionTypes: true,
});
addFormats(ajv);
const validateOrder = ajv.compile(orderSchema);
export function assertOrder(input: unknown): void {
if (validateOrder(input)) {
return;
}
const detail = (validateOrder.errors ?? []).map((errorItem) => ({
path: errorItem.instancePath || '/',
keyword: errorItem.keyword,
message: errorItem.message,
params: errorItem.params,
}));
throw new Error(`Order data failed validation: ${JSON.stringify(detail)}`);
}
मुख्य बिंदु:
compileकेवल एक बार कॉल करें, module top level पर; हर request पर दोबारा compile करना 10 ms की validation को सैकड़ों milliseconds में बदल देता है;strict: trueगलत लिखे keywords जल्दी उजागर करता है (जैसेminLengthके बजायminlength), इसलिए इसे चालू करना इसके लायक है;- केवल
ajv-formatsसेformat: "email"औरformat: "date-time"वास्तव में प्रभावी होते हैं; - 2020-12 को
ajv/dist/2020entry point चाहिए — डिफ़ॉल्ट export इस्तेमाल न करें।
6. Error messages कैसे design करें
Validator जो देता है वह है "कौन-सा keyword विफल हुआ"; उपयोगकर्ता जो चाहता है वह है "कौन-सा input box गलत है"। एक mapping परत जोड़ना इसके लायक है:
const FIELD_LABEL: Record<string, string> = {
'/name': 'Name',
'/age': 'Age',
'/tags': 'Tags',
};
/**
* Ajv errors को प्रदर्शन-योग्य संदेशों में बदलें।
* @param errors Ajv errors की सूची
* @returns प्रदर्शन संदेशों का array
*/
function toMessages(errors: Array<{ instancePath: string; keyword: string; params: unknown }>): string[] {
return errors.map((errorItem) => {
const label = FIELD_LABEL[errorItem.instancePath] ?? errorItem.instancePath;
if (errorItem.keyword === 'required') {
const missing = (errorItem.params as { missingProperty: string }).missingProperty;
return `${label} is missing the required field ${missing}`;
}
if (errorItem.keyword === 'minLength') {
return `${label} is too short`;
}
return `${label} failed validation (${errorItem.keyword})`;
});
}
API पक्ष पर machine-readable संरचना रखें ताकि front end तय कर सके कि इसे कैसे प्रस्तुत करना है:
{
"type": "https://bbdrug.com/problems/validation",
"title": "Request data validation failed",
"status": 422,
"errors": [
{ "path": "/items/0/price", "keyword": "pattern", "message": "Must be a monetary string with two decimal places" }
]
}
7. Performance और जाल
- Validation लागत schema जटिलता और डेटा आकार के साथ बढ़ती है; कई
oneOfbranches combinatorial explosion पैदा करते हैं, जो हज़ारों records के साथ बहुत स्पष्ट हो जाता है; uniqueItemsarray पर O(n²) deep comparison करता है; बड़े arrays के लिए business layer में deduplicate करें;patternमें catastrophic backtracking वाले पैटर्न (जैसे(a+)+$) से बचें; यह ReDoS का क्लासिक प्रवेश द्वार है;- जब
additionalProperties: falseallOfके साथ मिलाया जाता है, तो subschema में घोषित फ़ील्ड parent द्वारा "additional" माने जाते हैं, इसलिए आपको उन्हें एक ही schema में merge करना होगा याunevaluatedPropertiesइस्तेमाल करना होगा।
8. Checklist
- [ ] 2020-12 पिन करें और फ़ाइल के शीर्ष पर
$schemaघोषित करें; - [ ] साझा संरचनाएँ
$defs+$refसे निकालें, copy-paste से नहीं; - [ ]
oneOfके बजाय discriminators को प्राथमिकता दें; - [ ] Validator के
compileपरिणाम का दोबारा उपयोग करें और strict mode चालू करें; - [ ] स्पष्ट रूप से दस्तावेज़ित करें कि
formatलागू होता है या नहीं; - [ ] Error responses path, keyword और उपयोगकर्ता-मुखी संदेश देते हैं;
- [ ] Schema को CI में रखें: JSON Schema Test Suite cases को regression tests के रूप में चलाएँ।
FAQ
मुझे कौन-सा JSON Schema draft चुनना चाहिए?
नए प्रोजेक्ट्स के लिए 2020-12 इस्तेमाल करें और फ़ाइल के शीर्ष पर $schema घोषित करें। अगर schema OpenAPI 3.1 में जा रहा है, तो उसे भी 2020-12 होना चाहिए, क्योंकि 3.1 सीधे वही version embed करता है; draft-07 केवल उन प्रोजेक्ट्स के लिए रखें जो अभी भी पुरानी toolchains से बँधे हैं। items और $ref sibling keywords पर दोनों का व्यवहार अलग है, इसलिए एक schema समूह में इन्हें न मिलाएँ।
additionalProperties: false compatibility समस्याएँ क्यों बढ़ा देता है?
यह हर undeclared key को error मानता है, इसलिए client का एक अतिरिक्त फ़ील्ड पूरा payload विफल कर देता है। फिर API विकसित होते समय हर caller को एक साथ upgrade करना पड़ता है, वरना पुराने clients अस्वीकार हो जाते हैं। Config-शैली schemas को अतिरिक्त फ़ील्ड स्पष्ट रूप से बंद करने चाहिए, जबकि सार्वजनिक APIs को compatibility लागत तौलनी चाहिए।
क्या मैं oneOf या anyOf इस्तेमाल करूँ?
oneOf केवल तब इस्तेमाल करें जब डेटा को ठीक एक branch से मेल खाना ही हो। अगर branch बाधाएँ एक साथ पूरी हो सकती हैं, तो एक record कई branches से मेल खाता है और oneOf सीधे विफल हो जाता है; anyOf पर switch करें, या हर branch के अंदर const discriminator जोड़ें।