JSON Schema: মূল বিষয় থেকে প্রোডাকশন
সংক্ষেপে: কি-ওয়ার্ড অনেক, কিন্তু প্রতিদিন ব্যবহৃত হয় কেবল ডজনখানেক। type/properties/required/$ref ঠিক করুন, এরর মেসেজ ভালো সামলান, তাহলে বাস্তবের আশি শতাংশ ঢাকা পড়ে।

মূল কথা: আগে $schema-কে 2020-12-তে পিন করুন, তারপর $defs + $ref দিয়ে ভাগ করা কাঠামো বের করুন; oneOf কেবল যখন ঠিক একটি শাখা মিলতেই হবে তখন ব্যবহার করুন, বাকি সব জায়গায় discriminator বা anyOf-কে অগ্রাধিকার দিন।
স্কিমা ডকুমেন্টেশনের পরিশিষ্ট নয়; এটি এমন সম্পদ যা চালানো, পরীক্ষা ও জেনারেট করা যায়।
১. আগে সংস্করণ ঠিক করুন
| সংস্করণ | $schema মান |
অবস্থা | নোট |
|---|---|---|---|
| draft-04 | http://json-schema.org/draft-04/schema# |
পুরোনো | লিগ্যাসি প্রকল্প ও কিছু OpenAPI 2.0 টুলিং |
| draft-07 | http://json-schema.org/draft-07/schema# |
বহুল ব্যবহৃত | OpenAPI 3.0-এর বাস্তব ভিত্তি |
| 2019-09 | https://json-schema.org/draft/2019-09/schema |
প্রাপ্য | $defs ও টীকা শব্দভাণ্ডার চালু |
| 2020-12 | https://json-schema.org/draft/2020-12/schema |
বর্তমানে সুপারিশকৃত | prefixItems চালু, items-এর সেমান্টিক্স বদলেছে |
নতুন প্রকল্পে 2020-12 ব্যবহার করুন। খেয়াল করুন 2020-12-তে items কেবল "বাকি এলিমেন্ট" বাধ্যতামূলক করে — টাপল-শৈলীর ঘোষণা prefixItems-এ সরেছে।
draft-07 থেকে মাইগ্রেশনে এটাই সবচেয়ে সাধারণ ফাঁদ।
২. একটি ন্যূনতম ব্যবহারযোগ্য স্কিমা
{
"$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
}
এই স্কিমা চারটি সবচেয়ে সাধারণ চিন্তা ঢাকে: টাইপ বাধ্যবাধকতা, বাধ্যতামূলক ফিল্ড, রেঞ্জ সীমা, ও অ্যারে এলিমেন্ট বাধ্যবাধকতা।
৩. কি-ওয়ার্ড ম্যাপ
৩.১ সাধারণ
| কি-ওয়ার্ড | উদ্দেশ্য | সতর্কতা |
|---|---|---|
type |
টাইপ; অ্যারে হতে পারে | "type": ["string", "null"] মানে nullable |
enum |
এনাম মান | মান অবশ্যই অনন্য হতে হবে |
const |
একক-মান বাধ্যবাধকতা | সংস্করণ সংখ্যার মতো স্থির মানের জন্য ভালো |
$ref |
অন্য স্কিমা রেফারেন্স করে | পুরো স্কিমা রেফারেন্স করে, রেফারেন্সকারী অবজেক্টের পাশের কি-ওয়ার্ডের সঙ্গে মার্জ হয় না |
৩.২ সংখ্যা
multipleOf, maximum, exclusiveMaximum, minimum, exclusiveMinimum।
খেয়াল করুন draft-04-তে exclusiveMaximum ছিল বুলিয়ান (maximum-এর সঙ্গে ব্যবহৃত); কেবল draft-07 থেকে এটি স্বতন্ত্র সংখ্যিক কি-ওয়ার্ড —
যার ফলে "পুরোনো ডক থেকে কপি করা স্কিমা" অনেকগুলো strict মোডে ব্যর্থ হয়।
৩.৩ স্ট্রিং
minLength, maxLength, pattern (একটি ECMA-262 রেগুলার এক্সপ্রেশন), আর format (একটি টীকা)।
minLength ইউনিকোড কোড পয়েন্ট গোনে, UTF-16 একক নয়, বাইটও নয়। JavaScript-এর string.length দিয়ে যাচাই করলে অমিল হবে।
৩.৪ অ্যারে
| কি-ওয়ার্ড | উদ্দেশ্য |
|---|---|
prefixItems |
2020-12-এর টাপল-শৈলী বাধ্যবাধকতা, ইনডেক্স অনুযায়ী এলিমেন্ট মেলায় |
items |
বাকি এলিমেন্টের সমান বাধ্যবাধকতা (2020-12) |
contains + minContains/maxContains |
অন্তত/সর্বোচ্চ কতটি মিলে যাওয়া আইটেম থাকতে হবে |
uniqueItems |
এলিমেন্ট সবাই আলাদা হতে হবে (গভীর তুলনা; বড় অ্যারেতে এড়ান) |
minItems / maxItems |
দৈর্ঘ্য বাধ্যবাধকতা |
৩.৫ অবজেক্ট
properties, required, patternProperties, additionalProperties, propertyNames,
minProperties, maxProperties, dependentRequired, dependentSchemas।
তিনটি উচ্চ-কম্পাঙ্কের ভুল ধারণা:
requiredকেবল কি থাকা দাবি করে:{"name": ""}বৈধ। খালি স্ট্রিং প্রত্যাখ্যান করতেminLength: 1যোগ করতে হবে;additionalProperties: falseসামঞ্জস্য সমস্যাকে বড় করে: ক্লায়েন্টের একটি বাড়তি ফিল্ড পুরো পেললোড ব্যর্থ করে, তাই API বিবর্তনে প্রতিটি কলারকে একসঙ্গে আপগ্রেড করতে বাধ্য করে;propertiesঅঘোষিত কি সীমিত করে না — ডিফল্টে সেগুলো অনুমোদিত।
৩.৬ লজিক গঠন
{
"oneOf": [
{ "type": "object", "required": ["cardNumber"], "properties": { "cardNumber": { "type": "string" } } },
{ "type": "object", "required": ["accountId"], "properties": { "accountId": { "type": "string" } } }
]
}
| কি-ওয়ার্ড | সেমান্টিক্স | সাধারণ ব্যর্থতা |
|---|---|---|
allOf |
সবই পূরণ করতে হবে | একাধিক শাখা একই ফিল্ডে পরস্পরবিরোধী বাধ্যবাধকতা চাপায় |
anyOf |
অন্তত একটি পূরণ করতে হবে | এরর মেসেজ নির্দিষ্ট শাখায় ট্রেস করা কঠিন |
oneOf |
ঠিক একটি পূরণ করতে হবে | ডেটা দুই শাখা মেলে ব্যর্থ হয়, অথচ "স্পষ্টভাবে সঠিক" দেখায় |
not |
পূরণ করা যাবে না | কম ব্যবহৃত, এরর মেসেজ সবচেয়ে প্রতিকূল |
oneOf "বৈধ ডেটা ভ্যালিডেশনে ব্যর্থ" উৎপন্ন করার সবচেয়ে সম্ভাব্য কি-ওয়ার্ড। শাখা বাধ্যবাধকতা ওভারল্যাপ করলে (যেমন দুইটিই বাড়তি ফিল্ড অনুমোদন করে),
একটি রেকর্ড কয়েকটি শাখা মেলে আর oneOf সরাসরি ব্যর্থ হয়। সেক্ষেত্রে anyOf-তে যান, বা শাখাগুলোর ভেতরে 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-এর চেয়ে বেশি পাঠযোগ্য, আর ভালো এরর দেয়।
৪. ভাগ ও পুনরায় ব্যবহার
{
"$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ব্যবহার করে;- ফাইল-ছাড়ানো রেফারেন্স আপেক্ষিক
$idব্যবহার করে, কিন্তু প্রোডাকশনে রানটাইমে রিমোট স্কিমা ফেচের উপর নির্ভর করবেন না — স্ট্যাটিক ফাইল হিসেবে বান্ডল করুন; - রেফারেন্সের পাশে বাধ্যবাধকতা যোগ করা যায় না (
$ref-এর পাশের কি-ওয়ার্ডের আচরণ সংস্করণভেদে আলাদা)। গঠন দরকার হলেallOfব্যবহার করুন।
৫. Ajv দিয়ে রানটাইম ভ্যালিডেশন
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(`অর্ডার ডেটা ভ্যালিডেশনে ব্যর্থ: ${JSON.stringify(detail)}`);
}
মূল বিষয়:
compileকেবল একবার কল করুন, মডিউল টপ লেভেলে; প্রতিটি রিকোয়েস্টে আবার কম্পাইল করলে ১০ ms ভ্যালিডেশন শত মিলিসেকেন্ডে বদলায়;strict: trueভুল বানানের কি-ওয়ার্ড আগেই প্রকাশ করে (যেমনminLength-এর বদলেminlength), তাই চালু করা মূল্যবান;- কেবল
ajv-formatsদিলেইformat: "email"ওformat: "date-time"সত্যিই কার্যকর হয়; - 2020-12-এর জন্য
ajv/dist/2020এন্ট্রি পয়েন্ট দরকার — ডিফল্ট এক্সপোর্ট ব্যবহার করবেন না।
৬. এরর মেসেজ কীভাবে নকশা করবেন
ভ্যালিডেটর যা দেয় তা "কোন কি-ওয়ার্ড ব্যর্থ হয়েছে"; ব্যবহারকারী যা চায় তা "কোন ইনপুট বক্স ভুল"। একটি ম্যাপিং স্তর যোগ করা মূল্যবান:
const FIELD_LABEL: Record<string, string> = {
'/name': 'Name',
'/age': 'Age',
'/tags': 'Tags',
};
/**
* Convert Ajv errors into display-ready messages.
* @param errors The list of Ajv errors
* @returns An array of display messages
*/
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 দিকে মেশিন-পাঠযোগ্য কাঠামো রাখুন যাতে ফ্রন্টএন্ড কীভাবে উপস্থাপন করবে ঠিক করতে পারে:
{
"type": "https://bbdrug.com/problems/validation",
"title": "রিকোয়েস্ট ডেটা ভ্যালিডেশনে ব্যর্থ",
"status": 422,
"errors": [
{ "path": "/items/0/price", "keyword": "pattern", "message": "দুই দশমিক স্থানের টাকার স্ট্রিং হতে হবে" }
]
}
৭. পারফরম্যান্স ও ফাঁদ
- ভ্যালিডেশনের খরচ স্কিমা জটিলতা ও ডেটা আকারের সঙ্গে বাড়ে; একাধিক
oneOfশাখা combinatorial explosion ঘটায়, যা কয়েক হাজার রেকর্ডে খুব লক্ষণীয় হয়; uniqueItemsঅ্যারের উপর O(n²) গভীর তুলনা করে; বড় অ্যারেতে ব্যবসায়িক স্তরে ডুপ্লিকেটমুক্ত করুন;pattern-এ catastrophic backtracking প্যাটার্ন (যেমন(a+)+$) এড়ান; এটি ReDoS-এর ক্লাসিক প্রবেশবিন্দু;additionalProperties: falseallOf-এর সঙ্গে মিললে উপ-স্কিমায় ঘোষিত ফিল্ড প্যারেন্টে "বাড়তি" ধরা পড়ে, তাই সেগুলো একক স্কিমায় মার্জ করতে হবে বাunevaluatedPropertiesব্যবহার করতে হবে।
৮. চেকলিস্ট
- [ ] 2020-12 পিন করুন ও ফাইলের উপরে
$schemaঘোষণা করুন; - [ ] কপি-পেস্ট না করে
$defs+$refদিয়ে ভাগ করা কাঠামো বের করুন; - [ ]
oneOf-এর বদলে discriminator-কে অগ্রাধিকার দিন; - [ ] ভ্যালিডেটরের
compileফল পুনরায় ব্যবহার করুন ও strict মোড চালু করুন; - [ ]
formatবলবৎ হয় কি না স্পষ্টভাবে ডকুমেন্ট করুন; - [ ] এরর রেসপন্স পাথ, কি-ওয়ার্ড ও ব্যবহারকারী-মুখী মেসেজ দেয়;
- [ ] স্কিমা CI-তে রাখুন: JSON Schema Test Suite কেস রিগ্রেশন টেস্ট হিসেবে চালান।
সাধারণ প্রশ্ন
JSON Schema draft কীভাবে বাছাই করব?
নতুন প্রকল্পে 2020-12 ব্যবহার করুন আর ফাইলের উপরে $schema ঘোষণা করুন। স্কিমা OpenAPI 3.1-এ গেলে তাও 2020-12 হতে হবে, কারণ 3.1 সেই সংস্করণ সরাসরি এম্বেড করে; পুরোনো টুলচেইনে আটকে থাকা প্রকল্পে কেবল draft-07 রাখুন। items ও $ref-এর পাশের কি-ওয়ার্ডে দুইটির আচরণ আলাদা, তাই এক স্কিমা সেটে মিশাবেন না।
additionalProperties: false সামঞ্জস্য সমস্যাকে বড় করে কেন?
এটি প্রতিটি অঘোষিত কি-কে এরর ধরে, তাই ক্লায়েন্টের একটি বাড়তি ফিল্ড পুরো পেললোড ব্যর্থ করে। তখন API বিবর্তনে প্রতিটি কলারকে একসঙ্গে আপগ্রেড করতে হয়, নাহলে পুরোনো ক্লায়েন্ট প্রত্যাখ্যাত হয়। কনফিগ-শৈলীর স্কিমায় বাড়তি ফিল্ড স্পষ্টভাবে বন্ধ করা উচিত, অথচ পাবলিক API-তে সামঞ্জস্যের দাম বিবেচনা করতে হয়।
oneOf নাকি anyOf ব্যবহার করব?
ডেটা ঠিক একটি শাখা মিলতেই হবে কেবল তখন oneOf ব্যবহার করুন। শাখা বাধ্যবাধকতা একসঙ্গে সত্য হতে পারলে একটি রেকর্ড কয়েকটি শাখা মেলে আর oneOf সরাসরি ব্যর্থ হয়; anyOf-তে যান, বা প্রতিটি শাখার ভেতরে const discriminator যোগ করুন।