ساختن JSON Schema از روی یک نمونه واقعی، یکی از سریعترین راهها برای تبدیل داده خام به یک قرارداد قابل اعتبارسنجی است. وقتی یک پاسخ API، فایل تنظیمات یا مجموعه داده دارید، میتوانید ساختار اولیه Schema را از همان نمونه استخراج کنید؛ اما خروجی تولیدشده فقط یک نقطه شروع است. ابزار نمیتواند از یک یا چند نمونه محدود، تمام قواعد تجاری، فیلدهای اختیاری، محدودههای مجاز و نسخههای آینده داده را با قطعیت حدس بزند. بنابراین فرایند درست شامل تولید خودکار، بازبینی انسانی، افزودن محدودیتها و آزمایش با نمونههای معتبر و نامعتبر است.
پاسخ سریع: چگونه از روی JSON، Schema بسازیم؟
برای ساخت JSON Schema از روی داده واقعی، ابتدا JSON را از نظر Syntax معتبر کنید، سپس آن را به ابزار JSON Schema Generator بدهید تا نوع ریشه، Propertyها، نوع فیلدها، ساختار Arrayها و Objectهای تودرتو را استنتاج کند. بعد خروجی را بازبینی کنید: فیلدهای required، امکان null، محدودیتهای minLength و minimum، enumها، additionalProperties و Draft مورد استفاده را آگاهانه تنظیم کنید. در پایان Schema را با چند نمونه معتبر، نمونه ناقص، مقدار مرزی و داده اشتباه آزمایش کنید.
تولید JSON Schema دقیقاً چه کاری انجام میدهد؟
Generator داده ورودی را پیمایش میکند و برای هر مقدار یک الگوی احتمالی میسازد. اگر مقدار ریشه Object باشد، کلیدها به properties تبدیل میشوند؛ اگر Array باشد، ساختار اعضا برای items بررسی میشود. String، Number، Boolean و null نیز به Type متناظر نگاشت میشوند. خروجی معمولاً شامل type، properties، required و items است، اما جزئیات دقیق به ابزار و تنظیمات آن بستگی دارد.
| داده مشاهدهشده | استنتاج رایج | ریسک استنتاج |
|---|---|---|
"name": "Sara" | type: string | طول، الگو و امکان null مشخص نیست |
"age": 31 | type: integer | بازه مجاز و امکان مقدار اعشاری معلوم نیست |
"active": true | type: boolean | ممکن است فیلد اختیاری باشد |
"tags": ["api"] | Array از String | آرایه خالی یا اعضای چندنوعی دیده نشدهاند |
"profile": {...} | Object تودرتو | کلیدهای دیگری شاید در نمونههای دیگر باشند |
مثال ساده تبدیل JSON به JSON Schema
داده واقعی زیر را در نظر بگیرید:
{
"id": 101,
"name": "Wireless Mouse",
"price": 24.9,
"available": true,
"tags": ["wireless", "usb"]
}
یک Generator میتواند Schema اولیهای شبیه این بسازد:
{
"type": "object",
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"price": { "type": "number" },
"available": { "type": "boolean" },
"tags": {
"type": "array",
"items": { "type": "string" }
}
},
"required": ["id", "name", "price", "available", "tags"]
}
این خروجی ساختار مشاهدهشده را ثبت میکند، اما هنوز نمیگوید id باید مثبت باشد، name نباید خالی باشد، price نمیتواند منفی باشد یا tags حداکثر چند عضو دارد. این محدودیتها باید بعد از تولید اضافه شوند.
چرا یک نمونه برای تولید Schema کافی نیست؟
یک نمونه فقط یک حالت از داده را نشان میدهد. اگر در همان نمونه فیلدی وجود داشته باشد، Generator ممکن است آن را required فرض کند؛ در حالی که در قرارداد واقعی اختیاری است. برعکس، اگر مقدار null باشد، ابزار شاید نتواند نوع اصلی را تشخیص دهد. همچنین Array یکعضوی هیچ اطلاعاتی درباره تنوع اعضا، طول مجاز یا حالت خالی ارائه نمیکند.
نمونههای متعدد چه کمکی میکنند؟
با چند نمونه میتوان تفاوتها را بهتر دید. اگر فیلدی در همه نمونهها وجود داشته باشد، احتمال required بودن آن بیشتر است. اگر یک مقدار گاهی String و گاهی null باشد، Schema میتواند هر دو نوع را مجاز کند. بااینحال فراوانی در داده تاریخی الزاماً قرارداد رسمی نیست؛ ممکن است داده ناقص یا آلوده باشد. نمونهها شواهد هستند، نه تعریف نهایی.
فرایند پیشنهادی گامبهگام
- نمونههای واقعی و نماینده را جمعآوری کنید.
- اطلاعات حساس را حذف یا ناشناسسازی کنید.
- JSONها را از نظر Syntax بررسی کنید.
- Schema اولیه را با Generator بسازید.
- Draft و شناسه Schema را مشخص کنید.
- required و nullability را با قرارداد واقعی تطبیق دهید.
- محدودیتهای طول، بازه، enum و Pattern را اضافه کنید.
- ساختارهای تکراری را با
$defsو$refبازآرایی کنید. - نمونههای مثبت و منفی بسازید.
- Schema را در Validator و Runtime هدف آزمایش کنید.
مرحله اول: انتخاب داده نماینده
داده مناسب باید حالتهای معمول و مرزی را پوشش دهد. برای یک سفارش، فقط سفارش کامل و موفق کافی نیست؛ سفارش بدون تخفیف، با چند قلم، با آدرس متفاوت، با فیلد اختیاری و در صورت مجاز بودن با مقدار null را نیز در نظر بگیرید. هرچه تنوع ورودی بهتر باشد، استنتاج اولیه کمتر گمراهکننده خواهد بود.
از داده Production با احتیاط استفاده کنید
پاسخ API یا Log ممکن است نام، ایمیل، Token، شناسه شخصی یا اطلاعات پرداخت داشته باشد. قبل از انتقال داده به هر ابزار، مقدارهای حساس را حذف یا با داده ساختگی جایگزین کنید. اگر ابزار داخل مرورگر و بدون ارسال به سرور کار میکند، باز هم باید سیاست حریم خصوصی و رفتار واقعی پیادهسازی را بررسی کنید.
مرحله دوم: اعتبارسنجی Syntax پیش از تولید
Generator نمیتواند JSON خراب را به Schema قابل اعتماد تبدیل کند. کامای اضافه، کوتیشن اشتباه، کلید بدون کوتیشن و مقدارهایی مانند undefined باید پیش از تولید اصلاح شوند. معتبر بودن Syntax به معنی درست بودن داده نیست، اما شرط لازم برای Parse شدن آن است.
{
"name": "Nima",
"age": 28
}
این نمونه معتبر است. نسخهای که بعد از 28 کامای اضافه داشته باشد، پیش از هر استنتاجی باید اصلاح شود.
مرحله سوم: انتخاب Draft مناسب
JSON Schema چند Draft دارد و همه Validatorها امکانات یکسانی ندارند. اگر پروژه شما Draft 2020-12 را پشتیبانی میکند، Schema و Validator را روی همان نسخه هماهنگ کنید. ترکیب Keywordهای یک Draft با Validator قدیمی میتواند باعث نادیدهگرفتن قاعده یا خطای Compile شود.
{
"$id": "product.schema.json",
"type": "object"
}
در فایل واقعی میتوان Keyword مربوط به Dialect را نیز مطابق استاندارد و سیاست پروژه اضافه کرد. در این مقاله برای رعایت ممنوعیت لینک خارجی، مقدار URL آن نمایش داده نشده است.
مرحله چهارم: بازبینی required
یکی از مهمترین اصلاحها، تعیین Propertyهای اجباری است. Generator ممکن است همه کلیدهای نمونه را required کند. برای هر فیلد بپرسید: آیا نبودن آن مجاز است؟ آیا سرور مقدار پیشفرض میگذارد؟ آیا فقط در نوع خاصی از رکورد وجود دارد؟
{
"type": "object",
"properties": {
"id": { "type": "integer" },
"nickname": { "type": "string" }
},
"required": ["id"]
}
در اینجا nickname تعریف شده اما اختیاری است. وجود آن در properties بهتنهایی فیلد را اجباری نمیکند.
مرحله پنجم: مدلکردن null
اختیاری بودن و null بودن یکسان نیستند. فیلد اختیاری ممکن است اصلاً وجود نداشته باشد؛ فیلد nullable وجود دارد اما میتواند null باشد. اگر هر دو حالت مجازند، Schema باید هر دو را منعکس کند.
{
"type": "object",
"properties": {
"middleName": {
"type": ["string", "null"]
}
}
}
اگر Generator فقط نمونه "middleName": null ببیند، نوع واقعی String را نمیتواند با اطمینان استنتاج کند. برای این حالت به قرارداد، کد مدل یا نمونههای بیشتر رجوع کنید.
مرحله ششم: افزودن محدودیتهای معنایی
Schema تولیدشده بدون Constraint معمولاً بیشازحد باز است. محدودیتها باید بر اساس نیاز واقعی اضافه شوند:
minLengthوmaxLengthبرای Stringminimumوmaximumبرای NumberminItemsوmaxItemsبرای Arrayenumبرای مجموعه مقدارهای محدودpatternبرای قالب متنی مشخصadditionalPropertiesبرای کنترل کلیدهای ناشناخته
{
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 2,
"maxLength": 120
},
"price": {
"type": "number",
"minimum": 0
},
"status": {
"enum": ["draft", "published", "archived"]
}
},
"required": ["name", "price", "status"]
}
استنتاج Arrayها چه مشکلاتی دارد؟
Array خالی هیچ سرنخی درباره نوع items نمیدهد. Array یکعضوی نیز فقط یک نمونه از اعضا را نشان میدهد. اگر اعضا Object باشند، ممکن است Propertyهای اختیاری میان رکوردها متفاوت باشند. Generator باید چند عضو را ادغام کند، اما نحوه ادغام میان ابزارها متفاوت است.
Array همگن
{
"tags": ["json", "schema", "api"]
}
این داده بهطور منطقی Array از String است. سپس میتوان uniqueItems و محدودیت تعداد اعضا را اضافه کرد.
Array چندنوعی
{
"values": [10, "unknown", null]
}
ممکن است ابزار type را بهصورت مجموعهای از integer، string و null بسازد. قبل از پذیرفتن این خروجی بررسی کنید آیا تنوع واقعاً بخشی از قرارداد است یا نتیجه داده ناسازگار و قدیمی.
Objectهای تودرتو و ساختار تکراری
Generator معمولاً Schema تودرتو را مستقیماً داخل properties قرار میدهد. اگر ساختاری مانند Address در چند بخش تکرار شود، بهتر است آن را به $defs منتقل کنید:
{
"$defs": {
"address": {
"type": "object",
"properties": {
"city": { "type": "string" },
"postalCode": { "type": "string" }
},
"required": ["city", "postalCode"]
}
},
"type": "object",
"properties": {
"billingAddress": { "$ref": "#/$defs/address" },
"shippingAddress": { "$ref": "#/$defs/address" }
}
}
این بازآرایی تکرار را کم میکند و تغییر قرارداد را سادهتر میسازد.
چه زمانی enum را از داده استنتاج کنیم؟
دیدن سه مقدار متفاوت در نمونهها ثابت نمیکند که فقط همان سه مقدار مجازند. enum باید از قرارداد Domain، مستندات API یا مدل کد استخراج شود. تولید enum صرفاً از مجموعه داده تاریخی ممکن است مقدار جدید معتبر را در آینده رد کند.
نمونههای مشاهدهشده را با «فهرست کامل مقدارهای مجاز» اشتباه نگیرید.
additionalProperties را چگونه تنظیم کنیم؟
اگر خروجی باید قرارداد بسته و دقیق باشد، additionalProperties: false کلیدهای ناشناخته را رد میکند. این گزینه برای فایل تنظیمات کنترلشده یا Command داخلی مفید است. اما در APIهایی که ممکن است فیلد جدید اضافه کنند، Client سختگیر میتواند با تغییر سازگار نیز بشکند. تصمیم باید با سیاست Versioning هماهنگ باشد.
| حالت | مزیت | ریسک |
|---|---|---|
| مجاز بودن Property اضافی | سازگاری بیشتر با توسعه آینده | عبور کلید اشتباه تایپی |
additionalProperties: false | قرارداد دقیق و کشف سریع خطا | کاهش Forward Compatibility |
| Schema برای Property اضافی | کنترل نوع کلیدهای ناشناخته | پیچیدگی بیشتر |
ساخت Schema از روی چند فایل JSON
برای چند نمونه، ابتدا آنها را بر اساس نسخه و نوع رکورد گروهبندی کنید. ادغام کورکورانه سفارش، کاربر و محصول در یک Schema نتیجهای مبهم میدهد. در هر گروه، کلیدهای مشترک، کلیدهای اختیاری، نوعهای متفاوت و حالت null را استخراج کنید. اگر دو ساختار واقعاً Variantهای جدا هستند، از oneOf همراه Property تشخیصدهنده استفاده کنید.
{
"oneOf": [
{
"properties": {
"type": { "const": "email" },
"address": { "type": "string" }
},
"required": ["type", "address"]
},
{
"properties": {
"type": { "const": "sms" },
"phone": { "type": "string" }
},
"required": ["type", "phone"]
}
]
}
تولید Schema در JavaScript
در پروژه JavaScript میتوان نمونهها را Parse و به کتابخانه تولید Schema داد. API دقیق به کتابخانه و نسخه وابسته است، اما جریان کلی چنین است:
const samples = [
JSON.parse(firstJsonText),
JSON.parse(secondJsonText)
];
const schema = generateSchema(samples, {
draft: "2020-12"
});
console.log(JSON.stringify(schema, null, 2));
بعد از تولید، Schema را در تستها ثابت کنید. تغییر کتابخانه یا نسخه ممکن است ترتیب، Keywordها یا استراتژی ادغام نوعها را تغییر دهد.
تولید Schema در Python
import json
with open("sample.json", encoding="utf-8") as file:
data = json.load(file)
schema = infer_schema(data)
with open("schema.json", "w", encoding="utf-8") as file:
json.dump(schema, file, ensure_ascii=False, indent=2)
infer_schema در این مثال نماینده کتابخانه یا تابع انتخابی شماست. نام API و پشتیبانی Draft را از نسخه همان کتابخانه بررسی کنید. مهمتر از تولید، مرحله اصلاح و Validation بعدی است.
تولید Schema در C#
در .NET میتوان داده JSON را Parse کرد، نوع Tokenها را پیمایش کرد و با کتابخانه مناسب Schema ساخت. الگوی مفهومی:
var sample = ParseJson(jsonText);
var schema = GenerateSchema(sample);
var schemaText = SerializeIndented(schema);
کتابخانه انتخابی باید با Draft و Validator پروژه هماهنگ باشد. همچنین رفتار آن برای Decimal، Date، null، Array خالی و Propertyهای اختیاری را با تست واقعی بررسی کنید.
Schema تولیدشده را چگونه آزمایش کنیم؟
فقط اعتبارسنجی همان نمونه ورودی کافی نیست؛ چون Schema دقیقاً از همان نمونه ساخته شده و احتمال موفقیت آن زیاد است. مجموعه تست باید شامل این موارد باشد:
- نمونه معتبر معمولی
- نمونه با حذف هر فیلد required
- نوع اشتباه برای هر Property مهم
- حداقل و حداکثر مجاز
- مقدار درست خارج از نمونههای اولیه
- Array خالی و Array بسیار بزرگ
- Property اضافی
- null در فیلدهای nullable و non-nullable
- ساختار تودرتوی ناقص
نمونه تست منفی
{
"id": "101",
"name": "",
"price": -5,
"status": "deleted"
}
Schema خوب باید بر اساس قرارداد مشخص کند کدام بخشها نامعتبرند و مسیر خطا را واضح گزارش دهد.
خطاهای رایج در ساخت خودکار JSON Schema
اعتماد کامل به خروجی Generator
Generator فقط آنچه دیده را مدل میکند. خروجی بدون بازبینی میتواند required اشتباه، نوع محدود یا Schema بسیار باز داشته باشد.
ساخت enum از چند مقدار مشاهدهشده
این کار داده آینده را بیدلیل رد میکند. enum باید از قرارداد رسمی بیاید.
نادیده گرفتن null و فیلد اختیاری
نبودن فیلد با null بودن آن متفاوت است. هر دو را جداگانه مدل کنید.
استنتاج integer از یک عدد نمونه
اگر نمونه 10 باشد، Generator ممکن است integer بسازد؛ در حالی که قرارداد شاید 10.5 را نیز بپذیرد. نوع عدد را با Domain تطبیق دهید.
تولید Schema از داده آلوده
اگر داده تاریخی شامل خطا باشد، ابزار همان خطا را بهعنوان قرارداد یاد میگیرد. ابتدا کیفیت داده را بررسی کنید.
فراموش کردن Versioning
Schema تولیدشده عکس لحظهای از داده است. نسخه، مالک و تاریخ بازبینی آن را ثبت کنید.
نکات امنیتی و حریم خصوصی
- Token، رمز، کلید API و داده شخصی را وارد ابزار ناشناخته نکنید.
- پیش از اشتراک نمونه، مقادیر حساس را Mask کنید.
- برای ورودی بزرگ محدودیت اندازه و عمق بگذارید.
- Schema دریافتشده از کاربر را بدون کنترل Compile نکنید.
- Reference خارجی را در Validator محدود یا غیرفعال کنید.
- Patternهای پیچیده را از نظر ReDoS بررسی کنید.
نکات عملکرد برای دادههای بزرگ
تولید Schema از فایل بزرگ میتواند حافظه زیادی مصرف کند. در بسیاری از موارد، نمونهگیری هدفمند از رکوردها بهتر از بارگذاری کامل فایل است. نمونهگیری باید تنوع نوع، null، فیلدهای اختیاری و Variantها را حفظ کند. بعد از نهایی شدن Schema، آن را یکبار Compile و برای Validationهای بعدی Cache کنید.
بهترین روشها
- Generator را ابزار شروع بدانید، نه منبع حقیقت.
- از چند نمونه نماینده استفاده کنید.
- قرارداد API و مدل کد را کنار داده بررسی کنید.
- required، null و نوع Number را دستی بازبینی کنید.
- محدودیتها را از نیاز واقعی استخراج کنید.
- Schemaهای تکراری را با $defs و $ref ساده کنید.
- نمونههای معتبر و نامعتبر را در تست خودکار نگه دارید.
- Draft و نسخه Validator را ثابت و مستند کنید.
- تغییر Schema را مانند تغییر API بررسی کنید.
چکلیست نهایی ساخت JSON Schema از داده واقعی
- آیا JSON ورودی از نظر Syntax معتبر است؟
- آیا نمونهها نماینده حالتهای واقعی هستند؟
- آیا داده حساس حذف شده است؟
- آیا Draft درست انتخاب شده است؟
- آیا required با قرارداد واقعی تطبیق دارد؟
- آیا null و نبودن فیلد جدا شدهاند؟
- آیا Number و Integer درست انتخاب شدهاند؟
- آیا Array خالی و اعضای متفاوت بررسی شدهاند؟
- آیا enum از منبع معتبر آمده است؟
- آیا additionalProperties آگاهانه تنظیم شده است؟
- آیا محدودیتهای طول، بازه و تعداد اضافه شدهاند؟
- آیا تستهای مثبت و منفی اجرا شدهاند؟
جمعبندی
ساخت JSON Schema از روی داده واقعی، فرایند تعریف قرارداد را سریع میکند، اما نباید به تولید یک فایل خودکار ختم شود. Generator نوعها و ساختار اولیه را از نمونهها استنتاج میکند؛ سپس باید required، nullability، محدودیتها، enum، Versioning و سیاست Propertyهای اضافی را با دانش واقعی سیستم اصلاح کنید. بهترین نتیجه زمانی به دست میآید که چند نمونه نماینده، قرارداد API، مدل برنامه و تستهای مثبت و منفی کنار هم استفاده شوند. خروجی نهایی باید نه فقط نمونه امروز، بلکه داده معتبر آینده را نیز بهدرستی توصیف کند.