JSON Toolkit

التحقق من JSON في CI: خط أنابيب بـ jsonlint وajv وjq

الخلاصة: يثبت التحقق من الصياغة أن الملف يُحلل، ويثبت التحقق من المخطط أن الحقول تتبع العقد. في CI، المهم هو رموز خروج لا لبس فيها ومواقع أخطاء ومسارات إصلاح.

متوسطقراءة نحو 10 دقيقةنُشر في 2026-10-31حُدِّث في 2026-10-31
غلاف التحقق من JSON في CI

الخلاصة: في CI، دع jq empty يتولى الصياغة، وajv-cli يتولى البنية (check-jsonschema للفرق متعددة الصيغ)، وPrettier يتولى النمط؛ اربطها طبقة بطبقة وتوقف عند أول فشل.

اجتياز المحلل في محررك المحلي لا يعني أن الملف ينتمي إلى CI؛ واجتياز CI لا يعني أن الحقول تطابق العقد أيضًا.

1. افصل التحقق من الصياغة عن التحقق من المخطط

يُستخدم مصطلح "التحقق من JSON" على نطاق واسع أكثر من اللازم، ولهذا تختار الفرق غالبًا أداة واحدة لحل مشكلتين مختلفتين:

الطبقة السؤال المطروح الأدوات النموذجية ماذا يعني الفشل
الصياغة هل هذا الملف JSON صالح؟ jq empty، jsonlint، python -m json.tool الأقواس أو الفواصل أو علامات الاقتباس أو الترميز مكسورة
المخطط هل الحقول والأنواع والخصائص المطلوبة تطابق العقد؟ ajv-cli، check-jsonschema الملف يُحلل، لكن البنية التجارية خاطئة
النمط هل الإزاحة البادئة وترتيب المفاتيح متسقان؟ Prettier، jq -S التنسيق غير متسق والفروق مثيرة للضجيج

لا يمكن لهذه الطبقات أن تحل محل بعضها. الملف الذي يجتاز الصياغة قد يفشل في المخطط: {"price": "12.5"} صالح كـ JSON، لكن إن كان المخطط يشترط أن يكون price عددًا، فهو غير مقبول. وبالعكس، مهما كان المخطط كاملًا، فلن يُترجم ملف ينقصه قوس إغلاق مقابله أبدًا.

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

2. طبقة الصياغة: jq empty هو الخيار الأقل تبعية

إن كان jq موجودًا أصلًا في صورة CI، فالتحقق من الصياغة لا يكاد يكلف شيئًا:

jq empty data.json

لا ينتج jq empty أي خرج؛ إنه يحلل الملف فقط. يخرج بالرمز 0 عند النجاح ورمز غير صفري مع معلومات السطر/العمود عند الفشل. هذا يجعله أخف من سحب شجرة تبعيات Node كاملة، وبوابة أولى جيدة.

عند التحقق الدفعي من ملفات JSON في مستودع، استبعد node_modules ومخرجات البناء وذاكرات الأطراف الثالثة:

find . -name '*.json' \
  -not -path './node_modules/*' \
  -not -path './dist/*' \
  -print0 | xargs -0 -n1 jq empty

يعالج -print0 وxargs -0 المسافات في أسماء الملفات، بينما يضمن -n1 ملفًا واحدًا لكل استدعاء فيحدد رسالة الخطأ الجاني. لا تمرر مئات الملفات إلى عملية واحدة؛ فستتغطى رسائل الخطأ بعضها على بعض.

jsonlint خيار شائع آخر، يُستدعى عادة بـ npx jsonlint -q data.json، حيث يطبع -q الأخطاء فقط. يبلّغ عن أرقام الأسطر والأعمدة، لكن صيانته أقل نشاطًا من jq، وتختلف إصداراته في تسامحها مع BOM والتعليقات. اعتبره فاحص صياغة أوضح قراءةً، لا مدقق مخطط.

3. طبقة المخطط: أساسيات ajv-cli validate

Ajv هو مدقق المخططات الأكثر استخدامًا في منظومة JavaScript، ويكشفه ajv-cli كأداة سطر أوامر:

ajv validate -s schema.json -d data.json

يشير -s إلى ملف المخطط و-d إلى البيانات المدققة. عند الفشل تخرج العملية برمز غير صفري، فيفشل CI فورًا. تضيف المشاريع الحقيقية عادة علمين:

npx ajv-cli validate \
  -s schemas/order.json \
  -d 'fixtures/orders/*.json' \
  --strict=false \
  --errors=text

يساعد --strict=false مع المخططات الأقدم: إذ يعامل الوضع الصارم الكلمات المفتاحية غير المعروفة وإعلانات required المشبوهة كأخطاء، وهي ما تفشل فيه كثير من المخططات القديمة. ينتج --errors=text خرجًا مقروءًا للبشر، بينما تناسب بنية JSON الافتراضية الآلات أكثر. اختر بحسب من يستهلك النتيجة.

يجب أن يتطابق إصدار مسودة المخطط أيضًا. تدعم إصدارات Ajv الرئيسية مسودات مختلفة افتراضيًا، ويستطيع ajv-cli اختيار واحدة بـ --spec=draft7 أو --spec=draft2019 أو --spec=draft2020. إن أعلن مخطط ما عن $schema: "https://json-schema.org/draft/2020-12/schema" لكن الأداة تُترجمه كـ draft-07، فستُتجاهل بعض الكلمات المفتاحية بصمت. لمزيد عن تأليف المخططات وفروق الإصدارات، راجع البدء مع JSON Schema.

التحقق من الصيغة متطلب متكرر آخر. الكلمة المفتاحية format توضيحية فقط افتراضيًا؛ ولكي تفرض email وdate-time وuri شيئًا فعليًا، حمّل ajv-formats:

npx ajv-cli validate -c ajv-formats -s schema.json -d data.json

من السهل تفويت هذه الخطوة، والنتيجة أن حقل بريد يقبل not-an-email. قبل التسليم، أطعم خط الأنابيب سجلًا غير صالح عمدًا؛ فذلك يثبت أن القاعدة فعّالة أفضل من قراءة التوثيق عشر مرات.

check-jsonschema: صيغ متعددة وأخطاء ألطف

إن كانت الفرقة تتعامل مع YAML أو TOML أو إعداد GitHub Actions إضافة إلى JSON، فـ check-jsonschema أكثر ملاءمة:

check-jsonschema --schemafile schemas/config.json config/app.json
check-jsonschema --schemafile schemas/pipeline.json .github/workflows/ci.yml

مزاياه العملية:

  1. دعم YAML أصلي، يناسب الملفات "الشبيهة بـ JSON والمكتوبة بـ YAML"، مثل manifests الخاصة بـ Kubernetes وإعداد CI.
  2. تتضمن الأخطاء مسار الملف ورقم السطر ومسار الحقل، ما يقلل كلفة التحديد.
  3. يوفر خطاف pre-commit، فتكلفة الدمج منخفضة.
  4. يمكنه سحب مخططات شائعة من SchemaStore، لكنه يقبل أيضًا ملفات محلية، فيتجنب الاعتماد على الشبكة.

في الفرق متعددة اللغات، غالبًا ما يكون Python موجودًا أصلًا، فتركيب check-jsonschema وترقيته أخف من صيانة سلسلة أدوات Node. دع jq يتولى الصياغة وهذه الأداة تتولى البنية؛ فالتوليف منخفض الكلفة الصيانة جدًا.

4. رموز الخروج هي الإشارة الموثوقة الوحيدة في CI

مهما كان الخرج مصقولًا، لا يقرأ CI سوى رموز الخروج. ثلاث مصائد شائعة:

العَرَض السبب الإصلاح
توجد أخطاء لكن CI أخضر الأمر ينتهي بـ `
لا توجد أخطاء لكن CI أحمر يعامل diff "العثور على اختلافات" كفشل ميّز "الاختلاف" عن "خطأ التنفيذ"
خرج الأخطاء مقتطع خرج متعدد الملفات المتوازي يغطي بعضه بعضًا شغّل تسلسليًا أو اطبع لكل ملف

تذكر دلالات رمز الخروج في jq: التحليل الطبيعي يعيد 0؛ ومع -e، تعيد نتيجة التعبير false أو null الرمز 1؛ أخطاء الاستخدام والملفات المفقودة تعيد 2؛ وأخطاء ترجمة السكربت تعيد 3. تستخدم أدوات المخططات عمومًا 0 للنجاح و1 لفشل التحقق و2 لأخطاء الأداة أو الوسائط. عند كتابة غلاف، لا تحوّل كل رمز غير صفري إلى "البيانات خاطئة"، وإلا فسيُشخَّص فشل بيئي كخطأ محتوى.

5. pre-commit: التقط المشكلات قبل الإيداع

قيمة pre-commit ليست "فحصًا إضافيًا"، بل إعطاء المطورين ملاحظات في دقيقة بدلًا من انتظار تشغيل CI عشر دقائق. يبدو إعداد بسيط هكذا:

repos:
  - repo: https://github.com/python-jsonschema/check-jsonschema
    rev: 0.33.0
    hooks:
      - id: check-jsonschema
        files: ^configs/.*\.json$
        args:
          - --schemafile
          - schemas/config.json
  - repo: local
    hooks:
      - id: jq-syntax
        name: jq syntax check
        entry: jq empty
        language: system
        files: ^configs/.*\.json$

تفصيلان مهمان:

  • يجب أن يطابق files فقط المجلدات التي تحتاج التحقق. لا تشغّل الخطافات على node_modules أو الملفات المولّدة.
  • يفترض الخطاف المحلي أن jq مثبت على جهاز المطور. وثّق هذا المتطلب، أو انتقل إلى خطاف في حاوية.

إن كان المشروع يستخدم Husky وlint-staged أصلًا، فاجمعهما مباشرة:

{
  "lint-staged": {
    "configs/*.json": ["jq empty", "prettier --check"]
  }
}

تذكر أن lint-staged يمرر قائمة ملفات مهيأة، لذا يجب أن يقبل الأمر مسارات متعددة. يقبل ذلك كلٌّ من jq empty وprettier --check. لا تدع خطافًا يمسح المستودع كاملًا؛ فستصبح عمليات الإيداع أبطأ ملحوظًا.

6. GitHub Actions: خط أنابيب قابل للنسخ

ينفذ سير العمل التالي التحقق من الصياغة والتحقق من المخطط ويحتفظ بالتقرير عند الفشل:

name: json-validate

on:
  push:
    paths:
      - 'schemas/**'
      - 'fixtures/**'
  pull_request:

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - name: Syntax check
        run: |
          find fixtures -name '*.json' -print0 | xargs -0 -n1 jq empty
      - name: Schema check
        run: |
          npx --yes ajv-cli validate \
            -s schemas/order.json \
            -d 'fixtures/orders/*.json' \
            --strict=false \
            --errors=text

يمنع مرشّح paths التغييرات التوثيقية فقط من إطلاق التحقق. يناسب npx --yes المستودعات دون تبعيات مثبتة؛ أما مشاريع الإنتاج فتنبغي إضافة ajv-cli إلى devDependencies وتثبيت الإصدار. فدون قفل، قد يحوّل إصدارا جديد في المنبع يغيّر المسودة الافتراضية أو الوضع الصارم خط الأنابيب إلى أحمر بين ليلة وضحاها.

7. موقع الخطأ: من أرقام الأسطر إلى مسارات الحقول

نصف قيمة المدقق كشفية، والنصف الآخر تحديد موقع. تعلم قراءة ثلاثة أنواع من الخرج:

  1. أخطاء الصياغة: يبلّغ jq عن السطر والعمود، غالبًا مع مقتطف قريب. أصلح الأقواس والفواصل أولًا؛ لا تشتبه في الترميز فورًا.
  2. أخطاء المخطط: يشير instancePath في Ajv إلى موضع البيانات (مثل /items/0/price)، ويشير schemaPath إلى القاعدة (مثل #/properties/items/items/properties/price/type)، ويسمّي keyword القيد المنتهك.
  3. أخطاء الصيغة: عندما يفشل فحص format، تقول الرسالة غالبًا فقط "must match format "email"". ادمجها مع instancePath لتحديد الحقل.

الصق instancePath في سجل التحقيق؛ فهو أنفع بكثير من جدار من JSON. وفي المصفوفات، يخبرك الفهرس الرقمي أيضًا أي سجل فشل. إن كان المفتاح التجاري أكثر بداهة، فأضف سطر تعيين بعده، مثل items[0].id = ORD-042.

تُغطي أخطاء المخطط الشائعة، مثل الإفراط في additionalProperties: false أو كتابة فروع oneOf متداخلة فعليًا، في مصائد توليد JSON Schema الشائعة. قاعدة تحقق خاطئة أخطر من عدم التحقق، لأنها تخلق شعورًا زائفًا بالأمان.

8. جدول اختيار الأداة

الحاجة الأداة الموصى بها لماذا انتبه إلى
الصياغة فقط jq empty صفر تبعيات، سريع، رموز خروج واضحة المسافات في أسماء الملفات أثناء التشغيل الدفعي
أخطاء سطر/عمود ألطف jsonlint خرج مقروء نشاط الصيانة محدود
التحقق من JSON Schema ajv-cli منظومة ناضجة، أخطاء منظمة طابق إصدارات المسودة وحمّل دعم format
مستودع يخلط JSON وYAML check-jsonschema متعدد الصيغ، صديق لـ pre-commit مصدر المخطط والوصول إلى الشبكة
ملاحظات سريعة قبل الإيداع pre-commit / lint-staged يعالج الملفات المهيأة فقط يجب أن تقبل الخطافات مسارات متعددة
تنسيق متسق Prettier معتمد على نطاق واسع استخدم --check عندما لا تريد إعادة كتابة الملفات

توصية أخيرة: تحقق من أن خط الأنابيب يفشل فعلًا بإطعامه سجلًا غير صالح عمدًا أولًا. الخطر الأكبر ليس إعدادًا معقدًا؛ بل إعداد لا يسري أبدًا. معاملة "يمكن أن يفشل" كمعيار قبول أكثر موثوقية من معاملة "يجتاز" كمعيار. لسلسلة أدوات سطر الأوامر الأشمل، تابع المحررات وسطر الأوامر والأتمتة.

الأسئلة الشائعة

هل أستخدم jq empty أم jsonlint؟

فضّل jq empty في CI: صفر تبعيات، وبدء سريع، ودلالات رمز خروج واضحة. استخدم jsonlint عندما تحتاج رسائل سطر/عمود ألطف أو عندما تحقق محليًا. كلاهما يتحقق من الصياغة فقط؛ ولا يفحص أي منهما بنية الحقول.

لماذا يبلّغ ajv عن كلمة مفتاحية غير معروفة؟

أكد أولًا أن مسودة $schema في المخطط تطابق الإصدار الذي تستخدمه الأداة. كثير من أخطاء "الكلمة المفتاحية غير المعروفة" هي كلمات مفتاحية من مسودة جديدة يواجهها مترجم أقدم. إن تطابقت الإصدارات، فابحث عن خطأ كتابي. يمكن أن يُبقي --strict=false المخططات القديمة عاملة، لكن لا تعتمد عليه طويل الأمد لإخفاء مشكلات حقيقية.

يجتاز التحقق من المخطط، لكن API ما زال يفشل. لماذا؟

يتحقق المخطط من البنية لا من الدلالات التجارية. مثلًا، قد يكون status نصًا بقائمة enum قانونية، بينما تمنع آلة الحالة الانتقال مباشرة من paid إلى refunded. قواعد كهذه تنتمي إلى الشيفرة أو قيود قاعدة البيانات، لا إلى JSON Schema.

كيف أحدد الحقل الفاشل بسرعة؟

اقرأ instancePath أو رقم السطر من خرج الأداة. instancePath في Ajv هو JSON Pointer يشير مباشرة إلى البيانات؛ ويبلّغ check-jsonschema عن الملف والسطر. سجّل المسار وعيّنه على المفتاح التجاري؛ وتحديد المشكلة يستغرق عادة دقائق.

هل يجب أن يشغّل pre-commit تحققًا كاملًا؟

لا. يجب أن يعالج pre-commit الملفات المهيأة فقط لإبقاء الإيداعات سريعة. المسوح الكاملة تنتمي إلى CI أو الوظائف المجدولة. التقسيم بسيط: التشغيل المحلي يفضّل السرعة، وCI يفضّل الاكتمال.