الـ $defs والـ $ref جوه نفس الملف
order.json
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://koshary.example/schemas/order.json", "type": "object", "properties": { "shipping": { "$ref": "#/$defs/address" }, "billing": { "$ref": "#/$defs/address" } }, "required": ["shipping"], "$defs": { "address": { "type": "object", "properties": { "city": { "type": "string" }, "street": { "type": "string" } }, "required": ["city", "street"], "additionalProperties": false } }}- الـ
$defs: مخزن للـ schemas اللي هتتكرر. مش بيعمل validation لوحده، هو مجرد مكان. - الـ
$ref: “روح طابق الـ schema اللي هناك”. القيمة URI، والجزء بعد#اسمه JSON Pointer. - الـ
#/$defs/address: يعني من الـ root، ادخل$defs، وبعدينaddress. - الـ error بيبان في مكانه:
/shipping must have required property 'street'. - في draft-07: كان اسمه
definitions. لسه شغال في أغلب الأدوات، بس الاسم الرسمي في 2020-12 هو$defs.
ملفات منفصلة
كل schema في ملف، وكل ملف ليه $id. الـ $ref بيشاور على الـ $id:
reuse/├─ schemas/│ ├─ money.json ← $id: .../schemas/money.json│ └─ product.json ← $ref: "money.json"└─ index.jsschemas/money.json
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://koshary.example/schemas/money.json", "type": "object", "properties": { "amount": { "type": "integer", "minimum": 0 }, "currency": { "enum": ["EGP", "USD"] } }, "required": ["amount", "currency"], "additionalProperties": false}schemas/product.json
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://koshary.example/schemas/product.json", "type": "object", "properties": { "name": { "type": "string", "minLength": 2 }, "price": { "$ref": "money.json" }, "oldPrice": { "$ref": "money.json" }, "address": { "$ref": "#/$defs/address" } }, "required": ["name", "price"], "$defs": { "address": { "type": "object", "properties": { "city": { "type": "string" }, "street": { "type": "string" } }, "required": ["city", "street"] } }}index.js
import Ajv from "ajv/dist/2020.js";import { readFileSync, readdirSync } from "node:fs";const ajv = new Ajv({ allErrors: true });// حمّل كل الملفات مرة واحدة، والـ $id بتاع كل ملف هو اسمهfor (const file of readdirSync("./schemas")) { ajv.addSchema(JSON.parse(readFileSync(`./schemas/${file}`, "utf8")));}const validateProduct = ajv.getSchema("https://koshary.example/schemas/product.json");const ok = validateProduct({ name: "كشري كبير", price: { amount: 4500, currency: "EGP" } });console.log(ok);validateProduct({ name: "كشري", price: { amount: -5, currency: "EUR" }, address: { city: "Cairo" } });for (const e of validateProduct.errors) console.log(e.instancePath, e.message);output
true/price/amount must be >= 0/price/currency must be equal to one of the allowed values/address must have required property 'street'- الـ
"$ref": "money.json"relative: بيتحسب بالنسبة للـ$idبتاع الملف اللي هو فيه، فبقىhttps://koshary.example/schemas/money.json. - الـ
addSchema: بيسجّل الـ schema بالـ$idبتاعها من غير ما يعملها compile. - الـ
getSchema(id): بيعمل compile أول مرة ويخزّن الـ function، والمرات اللي بعدها بيرجّعها على طول. - الـ
$idمش لازم يفتح: Ajv مش بيعمل download لأي URL. لو مسجّلتش الـ schema، هتاخدcan't resolve reference https://... from id #.
الـ schemas المتداخلة (recursive)
شجرة categories، أو comments فيها replies، أو ملف فيه folders:
tree.js
import Ajv from "ajv/dist/2020.js";const ajv = new Ajv({ allErrors: true });const validate = ajv.compile({ $id: "https://koshary.example/schemas/category.json", type: "object", properties: { name: { type: "string" }, children: { type: "array", items: { $ref: "#" } }, }, required: ["name"],});const menu = { name: "الأكل", children: [{ name: "كشري", children: [{ title: "صغير" }] }],};console.log(validate(menu), validate.errors[0].instancePath, validate.errors[0].message);output
false /children/0/children/0 must have required property 'name'- الـ
"$ref": "#": يعني “الـ schema دي كلها من الـ root”، فكل child هو category. - الغلط اتلقط على عمق ٣: والـ
instancePathبيقولك بالظبط فين. - حط حد للعمق أو الحجم: الـ schema نفسها مش بتحدد عمق أقصى، فحد ممكن يبعت شجرة عمقها ١٠٠ ألف. حط
maxItemsعلى الـchildrenوlimit على حجم الـ body (فصل ١٢).
الـ $ref جنب keywords تانية
- في 2019-09 و2020-12: ينفع تكتب
{ "$ref": "#/$defs/name", "maxLength": 5 }، والاتنين بيتطبقوا. - في draft-07: الـ spec بتقول أي keyword جنب
$refيتجاهل. Ajv بيطبقهم برضه (اتجرّبت)، بس validators تانية لأ. متعتمدش عليها في draft-07، واستخدمallOf: [{ $ref }, { maxLength }].
الـ $anchor: اسم بدل path
anchor.json
{ "$id": "https://koshary.example/schemas/contact.json", "type": "object", "properties": { "phone": { "$ref": "#egPhone" } }, "$defs": { "phone": { "$anchor": "egPhone", "type": "string", "pattern": "^01[0125][0-9]{8}$" } }}- الـ
$anchor: اسم ثابت للـ subschema. لو نقلتها مكان تاني جوه الملف، الـ$refمش هيبوظ. - في Ajv 8.20 مع strict: اتجرّب ورمى
strict mode: unknown keyword: "$anchor". اشتغل لما ضفناajv.addKeyword("$anchor")قبل الـ compile، والـ ref اتحل صح. راجع الـ docs لأن ده ممكن يتصلح. - الـ
$idجوه subschema: نفس الفكرة، بيدي subschema اسم URI خاص بيها. نادرًا ما هتحتاجه.
الـ $dynamicRef في سطرين
- الفكرة: “generic” schemas. مثلًا schema لـ list عامة، وكل حد يستخدمها يحدد نوع العناصر، زي
List<T>. - في 2019-09 كان اسمه
$recursiveRef: واتغير لـ$dynamicRefو$dynamicAnchorفي 2020-12. - في الشغل اليومي: نادرًا ما هتحتاجه. وجرّبنا مثال generic list في Ajv 8.20 والعناصر اتراجعت على الـ schema الغلط، فلو محتاجه اختبره كويس على الـ validator بتاعك.
خلّصت الفصل لما
🧠 اختبر نفسك
١) كتبت $ref: user.json وAjv قال can't resolve reference. ليه؟
لأنك مسجّلتش schema بالـ
$id ده بـ addSchema، وAjv مش بيحمّل من النت.٢) شجرة comments recursive. إيه الخطر؟
حد يبعت عمق ضخم أو عدد عناصر ضخم. حط
maxItems وlimit على حجم الـ body.٣) الـ definitions ولا $defs؟
في 2019-09 و2020-12 الاسم الرسمي
$defs. الـ definitions بتاعة draft-07 وما قبلها.