JSON Toolkit

الأسئلة الشائعة حول الاستعلام والتحقق: JSONPath وSchema

الخلاصة: هل يجب أن تستخدم استعلامات المسارات JSONPath أم Pointer، وكيف تختار بين Patch وMerge Patch، وما الذي يمكن أن يمنعه Schema، وكيف تختار إصدار draft — كل ذلك موضّح في مرور واحد.

متوسطقراءة نحو 12 دقيقةنُشر في 2026-10-30حُدِّث في 2026-10-30
صورة غلاف للأسئلة الشائعة حول الاستعلام والتحقق

الخلاصة: استخدم JSONPath للعثور على العقد حسب الشرط، وJSON Pointer أو JSON Patch لعنونة المواضع الدقيقة وتحريرها، وMerge Patch لدمج التهيئة؛ وثبّت Schemas الجديدة على 2020-12 وفعّل allErrors لتعود كل الإخفاقات في مرور واحد.

الاستعلام هو "اعثر عليه"؛ والتحقق هو "تأكد من صحته". لكل منهما معياره، فلا تخلطهما.

1. استعلامات المسارات

كيف أختار بين JSONPath وJSON Pointer؟

يعتمد على ما إذا كنت "تبحث حسب الشرط" أم "تجلب حسب الموضع".

البعد JSONPath JSON Pointer
المعيار RFC 9535 (2024) RFC 6901 (2013)
التعبير $.users[?(@.age > 18)].id /users/0/id
أحرف البدل والتكرار يدعم * و.. غير مدعوم
الترشيح الشرطي مدعوم غير مدعوم
النتيجة مجموعة عقد (قد تكون عدة) موضع واحد
التعبيرية قوية، لكن مع إرث ثقيل من فروق التطبيقات ضعيفة، لكن حتمية بالكامل
الاستخدام النمطي استخراج السجلات، والتنقيح، واستكشاف البيانات JSON Patch، وعنونة التهيئة، ومسارات الأخطاء

قاعدة الاختيار مباشرة:

  • تحتاج إلى "الحصول على كل العقد المطابقة لشرط" ← JSONPath؛
  • تحتاج إلى "تثبيت موضع دقيق واحد وإضافة أو إزالة أو تعديل ما فيه" ← JSON Pointer؛
  • استخدمهما معًا: اعثر على العقدة بـ JSONPath ووصف موضعها بـ Pointer.

لا تستخدم JSONPath للعنونة في عقد API. JSONPath يعيد مجموعة، والفروع الثلاثة للنتائج الفارغة وذات العنصر الواحد ومتعددة العناصر تجعل البروتوكول معقدًا؛ أما Pointer فيعني "إما أنه يشير إلى موضع موجود أو أنه غير موجود"، وهذا يناسب العقد أكثر.

ما الفرق بين $.. و$.*؟

$.. هو النزول التكراري ويمر بكل مستوى؛ و$.* يعيد فقط الأبناء المباشرين للمستوى الحالي.

{
  "a": { "id": 1, "child": { "id": 2 } },
  "b": { "id": 3 }
}
  • $.* ← الكائنان {id:1, child:{...}} و{id:3}؛
  • $..id ← ثلاث قيم: 1 و2 و3؛
  • $.*.id ← 1 و3، دون 2 المتداخل.

النزول التكراري عملي لكنه مكلف: $.. يمر بالشجرة كلها، وهو O(n) على وثيقة كبيرة، ويلتقط بسهولة حقولًا عميقة لم تقصدها. إن كان مستوى محدد يكفي، فتجنب ...

هل يمكن للمرشحات مقارنة السلاسل النصية؟ وهل يمكنها المطابقة بتعبير نمطي؟

مقارنة السلاسل النصية تعمل؛ والتعبير النمطي يعتمد على التطبيق.

الصياغة القياسية (RFC 9535):

$.items[?(@.status == "active")]
$.items[?(@.price > 10 && @.stock > 0)]
$.items[?(@.name == $.target)]      // compare with another node

مقارنة السلاسل النصية تستخدم اقتباسات مزدوجة (الاقتباسات المفردة مسموحة أيضًا في RFC 9535، لكن بعض التطبيقات الأقدم تقبل الاقتباسات المزدوجة فقط). المقارنة العددية تتبع الدلالات العددية؛ وخلط سلسلة نصية مع عدد، مثل "10" > 9، سلوك غير محدد في المعيار، فلا تعتمد عليه.

أما التعبير النمطي، فيعرّف RFC 9535 الدالتين match() وsearch()، لكن دعمهما يختلف كثيرًا بين التطبيقات. المقاربة الآمنة: دع JSONPath يقوم بالترشيح الخشن، ونفّذ مطابقة التعبير النمطي في الكود بعد استرجاع النتائج.

هل أستخدم JSON Patch أم Merge Patch؟

كلاهما يصف "كيف تغيّر شيئًا"، لكن تعبيريتهما ودلالاتهما مختلفة تمامًا.

البعد JSON Patch (RFC 6902) Merge Patch (RFC 7386)
الشكل مصفوفة عمليات [{"op":"add",...}] كائن جزئي
العمليات المدعومة add / remove / replace / move / copy / test الدمج فقط (الحذف عبر null)
عنونة عناصر المصفوفة نعم، بالفهرس لا، تُستبدل المصفوفة كاملة
الذرية test يمكّن القفل المتفائل لا شيء
حجم الوثيقة كبير لكن دقيق صغير لكن محدود التعبيرية
يصلح لـ التحريرات الدقيقة، والتحديثات الجزئية في API دمج التهيئة، وإرسال نماذج جزئية

معايير القرار:

  • لتغيير حقل في العنصر الثالث من مصفوفة ← JSON Patch؛
  • لدمج عدة مدخلات تهيئة في التهيئة الموجودة ← Merge Patch؛
  • للحصول على تحكم تزامني من نوع "أفشل إن لم تكن القيمة الأصلية X" ← test في JSON Patch.

لاحظ الفخ في دلالة null في Merge Patch: null يعني "احذف هذا المفتاح"، لا "عيّن القيمة إلى null". تعيين قيمة إلى null مستحيل مع Merge Patch.

2. التحقق بـ Schema

ما المشكلات التي يحلها JSON Schema؟

يحوّل "كيف يجب أن تبدو البيانات" من اتفاق لفظي إلى عقد قابل للفحص بالآلة. الأخطاء التي يمكنه منعها فعلًا:

  • الحقول المطلوبة المفقودة (required)؛
  • النوع الخاطئ، مثل سلسلة نصية حيث يُتوقع عدد (type)؛
  • القيم خارج النطاق (minimum / maximum / minLength)؛
  • القيم خارج المجموعة المسموحة (enum)؛
  • أنواع عناصر المصفوفة غير المتسقة أو طول المصفوفة الخاطئ (items / minItems)؛
  • الحقول المفقودة في البنى المتداخلة (تحقق تكراري عبر properties)؛
  • الحقول الزائدة (additionalProperties: false).

ما لا يمكنه حله: قواعد العمل ("يجب أن يكون وقت الانتهاء بعد وقت البدء")، والشروط بين الحقول (يمكن التعبير عن بعضها بـif/then، لكن القراءة تتضرر)، وتميز قواعد البيانات، وفحوص الصلاحيات.

لذا Schema هو حارس "شكل البيانات"، وليس بديلًا عن منطق الأعمال.

هل أستخدم draft-07 أم 2020-12؟

استخدم 2020-12 للمشاريع الجديدة؛ وللأنظمة القائمة، تحقق مما يدعمه المدقق.

الإصدار الحالة التغييرات الرئيسية
draft-04 قديم كثير من المكتبات القديمة ما زال افتراضيها
draft-07 معيار فعلي أوسع دعم، وأغنى منظومة
2019-09 انتقالي أدخل $defs وunevaluatedProperties
2020-12 الموصى به حاليًا انقسم items إلى prefixItems + items، ويدعم $dynamicRef

فخان في الترحيل:

  1. تغيّرت صياغة الصفوف: في draft-07 كانت items ذات قيمة مصفوفية تعبّر عن صف؛ ونقلت 2020-12 ذلك إلى prefixItems؛
  2. $ref والكلمات الشقيقة: في draft-07 تُتجاهل الكلمات الشقيقة عند وجود $ref؛ و2020-12 يسمح بتعايشها.

إن كان المدقق يدعم draft-07 فقط، فكتابة صياغة 2020-12 على أي حال تُتجاهل بصمت — وأخطر حالة هي "نجح التحقق" حين لم يُتحقق من شيء أصلًا.

كيف أولّد أنواع TypeScript من Schema؟

مساران: اشتقاق الأنواع من Schema، أو توليد Schema من الأنواع.

من Schema إلى الأنواع (الخادم يعرّف العقد، والواجهة تستهلكه):

import type { FromSchema } from 'json-schema-to-ts';

const userSchema = {
  type: 'object',
  properties: {
    id: { type: 'string' },
    age: { type: 'integer' },
  },
  required: ['id'],
  additionalProperties: false,
} as const;

type User = FromSchema<typeof userSchema>;
// { id: string; age?: number }

المفتاح هو as const: بدونه سيُستنتج type: 'string' على أنه string بدلًا من الحرفي 'string'، فينهار الاستنتاج.

من الأنواع إلى Schema (الواجهة لديها الأنواع أصلًا وتحتاج إلى تحقق وقت التشغيل):

import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';

const userSchema = z.object({ id: z.string(), age: z.number().int().optional() });
const jsonSchema = zodToJsonSchema(userSchema);

ميزة هذا المسار أن "تعريفًا واحدًا ينتج الأنواع والتحقق وقت التشغيل معًا"؛ والتكلفة إدخال DSL، وقد يحمل Schema المولَّد تفاصيل تطبيقية (مثل بنية $ref غير جذابة).

كيف أحدد الحقل المحدد عندما يفشل التحقق؟

انظر إلى instancePath (في 2020-12) أو dataPath (في draft-07) الذي يعيده المدقق؛ إنه JSON Pointer إلى موضع الخطأ.

[
  {
    "instancePath": "/items/2/price",
    "schemaPath": "#/properties/items/items/properties/price/type",
    "keyword": "type",
    "message": "must be number"
  }
]

instancePath: "/items/2/price" يخبرك مباشرةً: حقل price في العنصر الثالث من مصفوفة items نوعه خاطئ.

اقتراحان عمليان:

  1. أعد كل الأخطاء مرة واحدة بدلًا من التوقف عند الأول — إصلاح الواجهة لكل شيء في مرور واحد أسرع من المحاولات ذهابًا وإيابًا؛
  2. حوّل Pointer إلى لغة بسيطة قبل عرضه للمستخدمين النهائيين؛ /items/2/price مفيد للمطورين وغير مفيد للمستخدمين.

allErrors: true هو المفتاح في معظم المدققات (Ajv يتطلب تفعيله صراحةً)؛ والافتراضي غالبًا وضع الإفشال السريع.

هل يجب تفعيل additionalProperties: false؟

فعّله للعقود الداخلية؛ وكن حذرًا مع APIs العامة.

بعد التفعيل، يفشل أي حقل غير معلن تحت properties في التحقق. الفائدة التقاط الأخطاء المطبعية (userNmae) والتمرير غير المتوقع؛ والعيب أن إضافة حقل من المنبع تكسر المستهلكين في المصب، فتدمر التوافق مع الإصدارات السابقة.

حل وسط:

السيناريو التوصية
بين الخدمات الداخلية فعّله. تغييرات الحقول تمر بإدارة الإصدارات
APIs خارجية عامة عطّله. اسمح للمنبع بإضافة الحقول
استهلاك بيانات طرف ثالث عطّله. أنت لا تتحكم في الطرف الآخر
تحقق التهيئة فعّله. خطأ مطبعي في التهيئة يجب أن يفشل فورًا

كيف أتحقق من أن عناصر المصفوفة فريدة؟

دلالتان مختلفتان — لا تخلط بينهما:

{ "uniqueItems": true }

هذا يزيل تكرار العنصر كاملًا، ويشترط ألّا تحتوي المصفوفة على عنصرين متساويين بعمق.

{
  "type": "array",
  "items": { "type": "object", "properties": { "id": { "type": "string" } } },
  "uniqueItems": true
}

المقطع أعلاه يضمن فقط ألّا تتكرر الكائنات ككل؛ فـ{"id":"a","name":"x"} و{"id":"a","name":"y"} لا يُعدان مكررين.

لإزالة التكرار حسب حقل محدد، لا يستطيع Schema التعبير عن ذلك؛ عليك التحقق في طبقة الأعمال:

const ids = items.map((item) => item.id);
if (new Set(ids).size !== ids.length) {
  throw new Error('id 重复');
}

هل format يتحقق فعلًا من البريد الإلكتروني والتواريخ؟

ليس بالضرورة. في المواصفة، format تعليق توضيحي افتراضيًا لا تأكيد، لذا قد يجمع المدقق عليه دون التحقق منه.

  • Ajv يتطلب تثبيت حزمة ajv-formats المنفصلة وإعدادها، وإلا فـformat: "email" خامل فعليًا؛
  • بعض المدققات تعامل format كتأكيد مفعّل افتراضيًا، فالسلوك غير متسق؛
  • حتى عند التفعيل، تعبير email النمطي فضفاض جدًا — فـa@b يمر.

الخلاصة: لا تعامل format كتحقق قوي. نفّذ تحققًا صارمًا للبريد الإلكتروني وURLs والتواريخ في كود الأعمال؛ والاحتفاظ بـformat في Schema كتوثيق أمر يستحق، لكن لا تتوقع أن يوقف البيانات القذرة.

3. مرجع سريع

السؤال الخلاصة
العثور على العقد حسب الشرط JSONPath
العنونة الدقيقة وتطبيق التصحيحات JSON Pointer
تغيير عناصر المصفوفة JSON Patch، وMerge Patch لا يستطيع
دمج التهيئة Merge Patch
إصدار Schema للمشاريع الجديدة 2020-12، وانتبه إلى prefixItems
توليد أنواع TS من Schema json-schema-to-ts + as const
تحديد أخطاء التحقق اقرأ instancePath، وفعّل allErrors
إزالة التكرار حسب حقل Schema لا يستطيع؛ استخدم Set في طبقة الأعمال
format: email معظم المدققات لا تفحصه افتراضيًا، فلا تعتمد عليه