Validasi JSON di CI: Pipeline dengan jsonlint, ajv, dan jq
Intinya: Validasi sintaks membuktikan file dapat di-parse; validasi schema membuktikan field mengikuti kontrak. Di CI, yang penting adalah exit code yang tidak ambigu, lokasi error, dan jalur perbaikan.

Intinya: di CI, biarkan jq empty menangani sintaks, ajv-cli menangani struktur (check-jsonschema untuk tim multi-format), dan Prettier menangani gaya; rangkai berdasarkan lapisan dan berhenti pada kegagalan pertama.
Lolos parser di editor lokal Anda tidak berarti file itu layak masuk CI; lolos CI juga tidak berarti field-nya sesuai kontrak.
1. Pisahkan validasi sintaks dari validasi schema
Frasa "validasi JSON" digunakan terlalu luas, itulah sebabnya tim sering memilih satu alat untuk memecahkan dua masalah berbeda:
| Lapisan | Pertanyaan yang dijawab | Alat tipikal | Arti kegagalan |
|---|---|---|---|
| Sintaks | Apakah file ini JSON yang valid? | jq empty, jsonlint, python -m json.tool |
Kurung, koma, tanda kutip, atau encoding rusak |
| Schema | Apakah field, tipe, dan properti wajib sesuai kontrak? | ajv-cli, check-jsonschema |
File dapat di-parse, tetapi struktur bisnisnya salah |
| Gaya | Apakah indentasi dan urutan kunci konsisten? | Prettier, jq -S |
Format tidak konsisten dan diff berisik |
Lapisan-lapisan ini tidak dapat saling menggantikan. File yang lolos sintaks masih bisa gagal schema: {"price": "12.5"} adalah JSON yang valid, tetapi jika schema mengharuskan price berupa angka, maka ia tidak dapat diterima. Sebaliknya, betapapun lengkap schema-nya, file yang kehilangan kurung tutup tidak akan pernah dapat dikompilasi terhadapnya.
Jadi pipeline harus menjalankan sintaks dulu, struktur kedua, dan gaya terakhir. Berhenti segera setelah satu lapisan gagal, jika tidak error parse akan menyamar sebagai error field.
2. Lapisan sintaks: jq empty adalah opsi dengan dependensi minimal
Jika jq sudah ada di image CI, validasi sintaks hampir tidak memakan biaya:
jq empty data.json
jq empty tidak menghasilkan output; ia hanya mem-parse file. Ia keluar dengan 0 saat berhasil dan kode non-nol plus informasi baris/kolom saat gagal. Itu membuatnya lebih ringan daripada menarik seluruh pohon dependensi Node, dan menjadi gerbang pertama yang baik.
Saat memvalidasi file JSON secara batch di repositori, kecualikan node_modules, output build, dan cache pihak ketiga:
find . -name '*.json' \
-not -path './node_modules/*' \
-not -path './dist/*' \
-print0 | xargs -0 -n1 jq empty
-print0 dan xargs -0 menangani spasi pada nama file, sementara -n1 memastikan satu file per pemanggilan agar pesan error mengidentifikasi biang keladinya. Jangan teruskan ratusan file ke satu proses; pesan error akan saling menimpa.
jsonlint adalah pilihan umum lainnya, biasanya dipanggil sebagai npx jsonlint -q data.json, di mana -q hanya mencetak error. Ia melaporkan nomor baris dan kolom, tetapi pemeliharaannya kurang aktif dibanding jq, dan versi berbeda menoleransi BOM serta komentar secara berbeda. Perlakukan ia sebagai pemeriksa sintaks yang lebih mudah dibaca, bukan sebagai validator schema.
3. Lapisan schema: dasar-dasar ajv-cli validate
Ajv adalah validator schema yang paling banyak digunakan di ekosistem JavaScript, dan ajv-cli mengeksposnya sebagai alat command-line:
ajv validate -s schema.json -d data.json
-s menunjuk ke file schema dan -d ke data yang divalidasi. Saat gagal, proses keluar dengan kode non-nol, sehingga CI langsung gagal. Proyek nyata biasanya menambahkan dua flag:
npx ajv-cli validate \
-s schemas/order.json \
-d 'fixtures/orders/*.json' \
--strict=false \
--errors=text
--strict=false membantu untuk schema lama: mode ketat memperlakukan kata kunci tak dikenal dan deklarasi required yang mencurigakan sebagai error, yang sering gagal pada banyak schema warisan. --errors=text menghasilkan output yang dapat dibaca manusia, sementara struktur JSON bawaan lebih baik untuk mesin. Pilih berdasarkan siapa yang mengonsumsi hasilnya.
Versi draft schema juga harus cocok. Versi mayor Ajv mendukung draft berbeda secara bawaan, dan ajv-cli dapat memilih salah satunya dengan --spec=draft7, --spec=draft2019, atau --spec=draft2020. Jika sebuah schema menyatakan $schema: "https://json-schema.org/draft/2020-12/schema" tetapi CLI mengompilasinya sebagai draft-07, sebagian kata kunci akan diabaikan secara diam-diam. Untuk lebih lanjut tentang penulisan schema dan perbedaan versi, lihat Memulai JSON Schema.
Validasi format adalah kebutuhan lain yang sering muncul. Kata kunci format hanya bersifat anotasi secara bawaan; agar email, date-time, dan uri benar-benar menegakkan sesuatu, muat ajv-formats:
npx ajv-cli validate -c ajv-formats -s schema.json -d data.json
Langkah ini mudah terlewat, dan akibatnya field email menerima not-an-email. Sebelum merilis, beri pipeline satu record yang sengaja tidak valid; itu membuktikan aturannya aktif jauh lebih baik daripada membaca dokumentasi sepuluh kali.
check-jsonschema: banyak format dan error yang lebih ramah
Jika tim menangani YAML, TOML, atau konfigurasi GitHub Actions selain JSON, check-jsonschema lebih nyaman:
check-jsonschema --schemafile schemas/config.json config/app.json
check-jsonschema --schemafile schemas/pipeline.json .github/workflows/ci.yml
Keunggulan praktisnya:
- Dukungan YAML native, yang cocok untuk file yang "berbentuk JSON tetapi ditulis dalam YAML", seperti manifest Kubernetes dan konfigurasi CI.
- Error mencakup path file, nomor baris, dan path field, sehingga biaya pelokasian rendah.
- Hook pre-commit disediakan, sehingga integrasinya murah.
- Ia dapat mengambil schema umum dari SchemaStore, tetapi juga menerima file lokal, menghindari dependensi jaringan.
Dalam tim lintas bahasa, Python sering sudah tersedia, sehingga memasang dan meningkatkan check-jsonschema lebih ringan daripada memelihara toolchain Node. Biarkan jq menangani sintaks dan alat ini menangani struktur; kombinasi ini memiliki biaya pemeliharaan yang sangat rendah.
4. Exit code adalah satu-satunya sinyal CI yang dapat dipercaya
Betapapun rapi outputnya, CI hanya membaca exit code. Tiga jebakan yang umum:
| Gejala | Penyebab | Perbaikan |
|---|---|---|
| Ada error tetapi CI hijau | Perintah diakhiri dengan ` | |
| Tidak ada error tetapi CI merah | diff memperlakukan "ditemukan perbedaan" sebagai kegagalan |
Bedakan "perbedaan" dari "error eksekusi" |
| Output error terpotong | Output multi-file paralel saling menimpa | Jalankan berurutan atau cetak per file |
Ingat semantik exit code jq: parsing normal mengembalikan 0; dengan -e, hasil ekspresi false atau null mengembalikan 1; error penggunaan dan file hilang mengembalikan 2; error kompilasi skrip mengembalikan 3. Alat schema umumnya menggunakan 0 untuk sukses, 1 untuk kegagalan validasi, dan 2 untuk error alat atau argumen. Saat menulis wrapper, jangan menyatukan setiap kode non-nol menjadi "datanya salah", atau kegagalan lingkungan akan salah didiagnosis sebagai masalah konten.
5. pre-commit: tangkap masalah sebelum commit
Nilai pre-commit bukan "satu pemeriksaan lagi"; melainkan memberi pengembang umpan balik dalam satu menit alih-alih setelah jalur CI sepuluh menit. Konfigurasi minimalnya terlihat seperti ini:
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$
Dua detail penting:
filesharus hanya cocok dengan direktori yang perlu divalidasi. Jangan menjalankan hook terhadapnode_modulesatau file yang dihasilkan.- Hook lokal mengasumsikan
jqterpasang di mesin pengembang. Dokumentasikan persyaratan itu, atau beralih ke hook dalam container.
Jika proyek sudah menggunakan Husky dan lint-staged, gabungkan langsung:
{
"lint-staged": {
"configs/*.json": ["jq empty", "prettier --check"]
}
}
Ingat bahwa lint-staged meneruskan daftar file yang di-stage, sehingga perintahnya harus menerima banyak path. Baik jq empty maupun prettier --check menerimanya. Jangan biarkan hook memindai seluruh repositori; commit akan menjadi jauh lebih lambat.
6. GitHub Actions: pipeline yang dapat Anda salin
Workflow di bawah menjalankan validasi sintaks, validasi schema, dan mempertahankan laporan saat gagal:
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
Filter paths mencegah perubahan yang hanya dokumentasi memicu validasi. npx --yes cocok untuk repositori tanpa dependensi yang dipatok; proyek produksi sebaiknya menambahkan ajv-cli ke devDependencies dan mematok versinya. Tanpa lock, rilis upstream yang mengubah draft bawaan atau mode ketat dapat membuat pipeline merah dalam semalam.
7. Pelokasian error: dari nomor baris ke path field
Separuh nilai validator adalah deteksi; separuh lainnya adalah pelokasian. Pelajari cara membaca tiga jenis output:
- Error sintaks:
jqmelaporkan baris dan kolom, sering dengan cuplikan di dekatnya. Perbaiki kurung dan koma terlebih dahulu; jangan langsung mencurigai encoding. - Error schema:
instancePathAjv menunjuk ke lokasi data (misalnya/items/0/price),schemaPathmenunjuk ke aturan (misalnya#/properties/items/items/properties/price/type), dankeywordmenamai batasan yang dilanggar. - Error format: ketika pemeriksaan
formatgagal, pesannya sering hanya mengatakan "must match format "email"". Gabungkan denganinstancePathuntuk mengidentifikasi field-nya.
Tempelkan instancePath ke catatan penyelidikan; itu jauh lebih berguna daripada dinding JSON. Untuk array, indeks numerik juga memberi tahu Anda record mana yang gagal. Jika kunci bisnis lebih intuitif, tambahkan baris pemetaan setelahnya, seperti items[0].id = ORD-042.
Kesalahan schema yang umum, seperti penggunaan additionalProperties: false secara berlebihan atau menulis cabang oneOf yang sebenarnya saling tumpang tindih, dibahas di Jebakan umum pembuatan JSON Schema. Aturan validasi yang salah lebih berbahaya daripada tidak ada validasi, karena menciptakan rasa aman yang palsu.
8. Tabel pemilihan alat
| Kebutuhan | Alat yang disarankan | Alasan | Yang perlu diwaspadai |
|---|---|---|---|
| Hanya sintaks | jq empty |
Nol dependensi, cepat, exit code jelas | Spasi pada nama file saat menjalankan batch |
| Error baris/kolom lebih ramah | jsonlint |
Output mudah dibaca | Aktivitas pemeliharaan terbatas |
| Validasi JSON Schema | ajv-cli |
Ekosistem matang, error terstruktur | Selaraskan versi draft dan muat dukungan format |
| Repositori campuran JSON dan YAML | check-jsonschema |
Multi-format, ramah pre-commit | Sumber schema dan akses jaringan |
| Umpan balik cepat sebelum commit | pre-commit / lint-staged | Hanya memproses file yang di-stage | Hook harus menerima banyak path |
| Format yang konsisten | Prettier | Diadopsi luas | Gunakan --check bila tidak ingin menulis ulang file |
Satu rekomendasi terakhir: pastikan pipeline benar-benar gagal dengan memberinya satu record yang sengaja tidak valid terlebih dahulu. Risiko terbesar bukanlah konfigurasi yang rumit; melainkan konfigurasi yang tidak pernah berlaku. Memperlakukan "ia dapat gagal" sebagai kriteria penerimaan lebih andal daripada memperlakukan "ia lolos" sebagai kriteria. Untuk toolchain command-line yang lebih luas, lanjutkan ke Editor, command line, dan otomasi.
FAQ
Haruskah saya menggunakan jq empty atau jsonlint?
Utamakan jq empty di CI: nol dependensi, startup cepat, dan semantik exit code yang jelas. Gunakan jsonlint ketika Anda membutuhkan pesan baris/kolom yang lebih ramah atau sedang menyelidiki secara lokal. Keduanya hanya melakukan validasi sintaks; tidak ada yang memeriksa struktur field.
Mengapa ajv melaporkan kata kunci tak dikenal?
Pertama pastikan draft $schema pada schema cocok dengan versi yang digunakan CLI. Banyak error "kata kunci tak dikenal" sebenarnya adalah kata kunci draft baru yang ditemui kompilator lama. Jika versinya cocok, periksa kesalahan ketik. --strict=false dapat menjaga schema warisan tetap berjalan, tetapi jangan mengandalkannya dalam jangka panjang untuk menyembunyikan masalah nyata.
Validasi schema lolos, tetapi API masih gagal. Mengapa?
Schema memvalidasi struktur, bukan semantik bisnis. Misalnya, status mungkin string dengan enum yang sah, sementara state machine melarang perpindahan langsung dari paid ke refunded. Aturan seperti itu berada di kode atau batasan basis data, bukan di JSON Schema.
Bagaimana cara menemukan field yang gagal dengan cepat?
Baca instancePath atau nomor baris dari output alat. instancePath Ajv adalah JSON Pointer yang menunjuk langsung ke data; check-jsonschema melaporkan file dan baris. Catat path dan petakan ke kunci bisnis; melokasikan masalah biasanya memakan beberapa menit.
Haruskah pre-commit menjalankan validasi penuh?
Tidak. pre-commit sebaiknya memproses hanya file yang di-stage agar commit tetap cepat. Pemindaian penuh milik CI atau job terjadwal. Pembagiannya sederhana: eksekusi lokal mengutamakan kecepatan, CI mengutamakan kelengkapan.