اعتبارسنجی JSON در CI: خط لولهای با jsonlint، ajv و jq
جمعبندی: اعتبارسنجی نحو ثابت میکند فایل تجزیه میشود؛ اعتبارسنجی schema ثابت میکند فیلدها از قرارداد پیروی میکنند. در CI آنچه اهمیت دارد کدهای خروج بیابهام، مکان خطا و مسیر ترمیم است.

جمعبندی: در CI بگذارید jq empty نحو را، ajv-cli ساختار را (check-jsonschema برای تیمهای چندقالبی) و Prettier سبک را مدیریت کند؛ آنها را لایهبهلایه زنجیر کنید و در نخستین شکست متوقف شوید.
عبور از تجزیهکننده در ویرایشگر محلی یعنی فایل به CI تعلق دارد؟ نه. عبور از CI هم یعنی فیلدها با قرارداد میخوانند؟ باز هم نه.
۱. اعتبارسنجی نحو را از اعتبارسنجی schema جدا کنید
عبارت «اعتبارسنجی JSON» بسیار فراتر از معنایش استفاده میشود و همین دلیل آن است که تیمها اغلب یک ابزار را برای دو مسئله متفاوت انتخاب میکنند:
| لایه | به کدام پرسش پاسخ میدهد | ابزارهای معمول | شکست چه معنایی دارد |
|---|---|---|---|
| نحو | آیا این فایل JSON معتبر است؟ | jq empty، jsonlint، python -m json.tool |
براکت، ویرگول، گیومه یا رمزگذاری شکسته است |
| Schema | آیا فیلدها، نوعها و ویژگیهای required با قرارداد میخوانند؟ | ajv-cli، check-jsonschema |
فایل تجزیه میشود، اما ساختار کسبوکار اشتباه است |
| سبک | آیا تورفتگی و ترتیب کلید یکسان است؟ | Prettier، jq -S |
قالببندی ناهمگون است و diffها پرنویز |
این لایهها جایگزین یکدیگر نمیشوند. فایلی که نحو را میگذراند هنوز میتواند در schema شکست بخورد: {"price": "12.5"} JSON معتبر است، اما اگر schema ایجاب کند price عدد باشد، قابل قبول نیست. برعکس، هرقدر schema کامل باشد، فایلی که یک آکولاد بسته کم دارد هرگز در برابر آن کامپایل نمیشود.
پس خط لوله باید نخست نحو، دوم ساختار و آخر سبک را اجرا کند. بهمحض شکست یک لایه متوقف شوید، وگرنه خطای تجزیه بهصورت خطای فیلد پنهان میشود.
۲. لایه نحو: jq empty گزینه با کمترین وابستگی
اگر jq از پیش در ایمیج CI هست، اعتبارسنجی نحو تقریباً هیچ هزینهای ندارد:
jq empty data.json
jq empty هیچ خروجی تولید نمیکند؛ فقط فایل را تجزیه میکند. موفقیت با ۰ و شکست با کد غیرصفر بههمراه اطلاعات خط/ستون خارج میشود. همین آن را سبکتر از کشیدن یک درخت وابستگی کامل Node میکند و دروازه نخست خوبی است.
هنگام اعتبارسنجی دستهای فایلهای JSON در مخزن، node_modules، خروجی build و کشهای شخص ثالث را حذف کنید:
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 و کامنت را متفاوت تحمل میکنند. آن را یک بررسیکننده نحو خوانا بدانید، نه اعتبارسنج schema.
۳. لایه Schema: مبانی ajv-cli validate
Ajv پرکاربردترین اعتبارسنج schema در اکوسیستم JavaScript است و ajv-cli آن را بهصورت ابزار خط فرمان عرضه میکند:
ajv validate -s schema.json -d data.json
-s به فایل schema و -d به داده در حال اعتبارسنجی اشاره میکند. در شکست، فرآیند با کد غیرصفر خارج میشود، پس CI بیدرنگ شکست میخورد. پروژههای واقعی معمولاً دو پرچم میافزایند:
npx ajv-cli validate \
-s schemas/order.json \
-d 'fixtures/orders/*.json' \
--strict=false \
--errors=text
--strict=false با schemaهای قدیمیتر کمک میکند: حالت strict کلمات کلیدی ناشناخته و اعلانهای مشکوک required را خطا میداند، که بسیاری از schemaهای میراثی آن را شکست میدهند. --errors=text خروجی خوانا برای انسان میدهد، در حالی که ساختار JSON پیشفرض برای ماشین بهتر است. بر اساس مصرفکننده نتیجه انتخاب کنید.
نسخه draft schema هم باید بخواند. نسخههای اصلی Ajv بهطور پیشفرض draftهای مختلف را پشتیبانی میکنند و ajv-cli میتواند با --spec=draft7، --spec=draft2019 یا --spec=draft2020 یکی را انتخاب کند. اگر schema اعلام کند $schema: "https://json-schema.org/draft/2020-12/schema" اما CLI آن را بهعنوان draft-07 کامپایل کند، برخی کلمات کلیدی بیصدا نادیده گرفته میشوند. برای اطلاعات بیشتر درباره نگارش schema و تفاوت نسخهها، شروع کار با JSON Schema را ببینید.
اعتبارسنجی format نیاز پرتکرار دیگری است. کلمه کلیدی 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: چند قالب و خطاهای دوستانهتر
اگر تیم علاوه بر JSON با پیکربندی YAML، TOML یا GitHub Actions سر و کار دارد، check-jsonschema راحتتر است:
check-jsonschema --schemafile schemas/config.json config/app.json
check-jsonschema --schemafile schemas/pipeline.json .github/workflows/ci.yml
مزیتهای عملیاش:
۱. پشتیبانی بومی YAML که برای فایلهایی «با شکل JSON اما نوشتهشده در YAML» مانند manifestهای Kubernetes و پیکربندی CI مناسب است. ۲. خطاها شامل مسیر فایل، شماره خط و مسیر فیلد هستند و هزینه مکانیابی را پایین نگه میدارند. ۳. قلاب pre-commit فراهم شده، پس یکپارچهسازی ارزان است. ۴. میتواند schemaهای رایج را از SchemaStore بگیرد، اما فایلهای محلی هم میپذیرد و از وابستگی شبکه پرهیز میکند.
در تیمهای چندزبانه، Python اغلب از پیش حاضر است، پس نصب و ارتقای check-jsonschema سبکتر از نگهداری زنجیره Node است. بگذارید jq نحو و این ابزار ساختار را مدیریت کند؛ ترکیب هزینه نگهداری بسیار کمی دارد.
۴. کدهای خروج تنها سیگنال قابل اعتماد CI هستند
هرقدر خروجی صیقلخورده باشد، CI فقط کدهای خروج را میخواند. سه تله رایج است:
| نشانه | علت | رفع |
|---|---|---|
| خطا وجود دارد اما CI سبز است | دستور با ` | |
| خطایی نیست اما CI قرمز است | diff «تفاوت پیدا شد» را شکست میداند |
«تفاوت» را از «خطای اجرا» جدا کنید |
| خروجی خطا بریده است | خروجی موازی چند فایل روی خود مینویسد | سریال اجرا کنید یا هر فایل را جدا چاپ کنید |
معنای کد خروج jq را به یاد داشته باشید: تجزیه عادی ۰ برمیگرداند؛ با -e، نتیجه عبارت false یا null مقدار ۱ برمیگرداند؛ خطاهای استفاده و فایلهای غایب ۲ و خطاهای کامپایل اسکریپت ۳. ابزارهای schema عموماً ۰ برای موفقیت، ۱ برای شکست اعتبارسنجی و ۲ برای خطاهای ابزار یا آرگومان استفاده میکنند. هنگام نوشتن wrapper، هر کد غیرصفر را به «داده اشتباه است» فروپاش نکنید، وگرنه خطای محیط بهعنوان مشکل محتوا تشخیص داده میشود.
۵. pre-commit: پیش از 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 فهرستی از فایلهای staged میفرستد، پس دستور باید چند مسیر بپذیرد. jq empty و prettier --check هر دو این کار را میکنند. نگذارید قلاب کل مخزن را اسکن کند؛ commitها بهطور محسوس کند میشوند.
۶. GitHub Actions: خط لولهای که میتوانید کپی کنید
گردش کار زیر اعتبارسنجی نحو و schema را اجرا میکند و گزارش را در شکست نگه میدارد:
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 بیفزایند و نسخه را تثبیت کنند. بدون lock، انتشار بالادستی که draft یا حالت strict پیشفرض را تغییر دهد میتواند خط لوله را یکشبه قرمز کند.
۷. مکان خطا: از شماره خط تا مسیر فیلد
نیمی از ارزش یک اعتبارسنج تشخیص است؛ نیم دیگر مکانیابی. خواندن سه نوع خروجی را یاد بگیرید:
۱. خطاهای نحوی: jq خط و ستون را گزارش میکند، اغلب با قطعهای نزدیک. نخست براکتها و ویرگولها را اصلاح کنید؛ بیدرنگ به رمزگذاری مشکوک نشوید.
۲. خطاهای Schema: instancePath در Ajv به مکان داده اشاره میکند (مثلاً /items/0/price)، schemaPath به قاعده (مثلاً #/properties/items/items/properties/price/type) و keyword قید نقضشده را نام میبرد.
۳. خطاهای Format: وقتی بررسی format شکست میخورد، پیام اغلب فقط میگوید «must match format "email"». آن را با instancePath ترکیب کنید تا فیلد را شناسایی کنید.
instancePath را در سابقه بررسی بچسبانید؛ بسیار مفیدتر از دیوار JSON است. برای آرایهها، ایندکس عددی هم میگوید کدام رکورد شکست خورده. اگر کلید کسبوکار شهودیتر است، پس از آن یک خط نگاشت بیفزایید، مثلاً items[0].id = ORD-042.
اشتباهات رایج schema، مانند استفاده بیشازحد از additionalProperties: false یا نوشتن شاخههای oneOf که در واقع همپوشانی دارند، در تلههای رایج تولید JSON Schema پوشش داده شدهاند. قاعده اعتبارسنجی اشتباه خطرناکتر از نبود اعتبارسنجی است، چون حس کاذب امنیت میسازد.
۸. جدول انتخاب ابزار
| نیاز | ابزار پیشنهادی | چرا | مراقب چه باشید |
|---|---|---|---|
| فقط نحو | jq empty |
صفر وابستگی، سریع، کد خروج روشن | فاصله در نام فایل هنگام اجرای دستهای |
| خطای خط/ستون دوستانهتر | jsonlint |
خروجی خوانا | فعالیت نگهداری محدود است |
| اعتبارسنجی JSON Schema | ajv-cli |
اکوسیستم بالغ، خطاهای ساختاریافته | نسخه draft را همتراز و پشتیبانی format را بار کنید |
| مخزن مختلط JSON و YAML | check-jsonschema |
چندقالبی، سازگار با pre-commit | منبع schema و دسترسی شبکه |
| بازخورد سریع پیش از commit | pre-commit / lint-staged | فقط فایلهای staged را پردازش میکند | قلابها باید چند مسیر بپذیرند |
| قالببندی یکسان | Prettier | پذیرش گسترده | وقتی نمیخواهید فایلها بازنویسی شوند از --check استفاده کنید |
یک توصیه پایانی: نخست با دادن یک رکورد عمداً نامعتبر تأیید کنید خط لوله واقعاً شکست میخورد. بزرگترین خطر پیکربندی پیچیده نیست؛ پیکربندیای است که هرگز اثر نمیکند. «میتواند شکست بخورد» را معیار پذیرش دانستن از «عبور میکند» قابل اعتمادتر است. برای زنجیره ابزار گستردهتر خط فرمان، با ویرایشگرها، خط فرمان و خودکارسازی ادامه دهید.
پرسشهای متداول
از jq empty استفاده کنم یا jsonlint؟
در CI jq empty را ترجیح دهید: صفر وابستگی، راهاندازی سریع و معنای کد خروج روشن. وقتی به پیامهای خط/ستون دوستانهتر نیاز دارید یا محلی بررسی میکنید از jsonlint استفاده کنید. هر دو فقط اعتبارسنجی نحو انجام میدهند؛ هیچکدام ساختار فیلد را بررسی نمیکند.
چرا ajv کلمه کلیدی ناشناخته گزارش میکند؟
نخست تأیید کنید draft $schema با نسخهای که CLI استفاده میکند میخواند. بسیاری از خطاهای «کلمه کلیدی ناشناخته» کلمات کلیدی draft جدید هستند که کامپایلر قدیمیتر با آنها روبهرو میشود. اگر نسخهها میخوانند، تایپو را بررسی کنید. --strict=false میتواند schemaهای میراثی را کارا نگه دارد، اما بلندمدت به آن تکیه نکنید تا مشکلات واقعی را پنهان کند.
اعتبارسنجی Schema عبور میکند اما API هنوز شکست میخورد. چرا؟
Schema ساختار را اعتبارسنجی میکند، نه معنای کسبوکار. مثلاً status ممکن است رشتهای با enum مجاز باشد، در حالی که ماشین حالت حرکت مستقیم از paid به refunded را منع میکند. چنین قواعدی به کد یا قیدهای پایگاهداده تعلق دارند، نه JSON Schema.
چگونه فیلد شکستخورده را سریع پیدا کنم؟
instancePath یا شماره خط را از خروجی ابزار بخوانید. instancePath در Ajv یک JSON Pointer است که مستقیم به داده اشاره میکند؛ check-jsonschema فایل و خط را گزارش میکند. مسیر را ثبت و به کلید کسبوکار نگاشت کنید؛ مکانیابی مشکل معمولاً چند دقیقه میبرد.
آیا pre-commit باید اعتبارسنجی کامل اجرا کند؟
نه. pre-commit باید فقط فایلهای staged را پردازش کند تا commitها سریع بمانند. اسکنهای کامل به CI یا کارهای زمانبندیشده تعلق دارند. تقسیم کار ساده است: اجرای محلی سرعت را ترجیح میدهد، CI کامل بودن را.