Kiểm chứng JSON trong CI: pipeline với jsonlint, ajv và jq
Kết luận: Kiểm chứng cú pháp chứng minh tệp phân tích được; kiểm chứng schema chứng minh các trường tuân theo hợp đồng. Trong CI, điều quan trọng là mã thoát rõ ràng, định vị lỗi và đường sửa.

Kết luận: trong CI, để jq empty lo cú pháp, ajv-cli lo cấu trúc (check-jsonschema cho nhóm đa định dạng), và Prettier lo phong cách; nối chúng theo lớp và dừng ở lần thất bại đầu tiên.
Qua được parser trong trình soạn thảo cục bộ không có nghĩa tệp thuộc về CI; qua được CI cũng không có nghĩa các trường khớp hợp đồng.
1. Tách kiểm chứng cú pháp khỏi kiểm chứng schema
Cụm "kiểm chứng JSON" bị dùng quá rộng, nên các nhóm thường chọn một công cụ để giải hai vấn đề khác nhau:
| Lớp | Câu hỏi được trả lời | Công cụ điển hình | Lỗi nghĩa là gì |
|---|---|---|---|
| Cú pháp | Tệp này có phải JSON hợp lệ không? | jq empty, jsonlint, python -m json.tool |
Ngoặc, dấu phẩy, dấu nháy hoặc mã hoá bị hỏng |
| Schema | Các trường, kiểu và thuộc tính bắt buộc có khớp hợp đồng không? | ajv-cli, check-jsonschema |
Tệp phân tích được, nhưng cấu trúc nghiệp vụ sai |
| Phong cách | Thụt lề và thứ tự khóa có nhất quán không? | Prettier, jq -S |
Định dạng không nhất quán và diff nhiễu |
Các lớp này không thể thay thế nhau. Một tệp qua cú pháp vẫn có thể trượt schema: {"price": "12.5"} là JSON hợp lệ, nhưng nếu schema yêu cầu price là số thì nó không được chấp nhận. Ngược lại, schema đầy đủ đến đâu thì một tệp thiếu ngoặc đóng cũng không bao giờ biên dịch được với nó.
Nên pipeline nên chạy cú pháp trước, cấu trúc thứ hai, phong cách cuối. Hãy dừng ngay khi một lớp thất bại, nếu không lỗi phân tích sẽ hóa trang thành lỗi trường.
2. Lớp cú pháp: jq empty là lựa chọn ít phụ thuộc nhất
Nếu jq đã có trong image CI, kiểm chứng cú pháp gần như không tốn gì:
jq empty data.json
jq empty không tạo đầu ra; nó chỉ phân tích tệp. Nó thoát với 0 khi thành công và với mã khác 0 kèm thông tin dòng/cột khi thất bại. Điều đó khiến nó nhẹ hơn việc kéo cả một cây phụ thuộc Node, và là cổng kiểm tra đầu tiên tốt.
Khi kiểm chứng hàng loạt tệp JSON trong một kho mã, hãy loại node_modules, đầu ra build và cache bên thứ ba:
find . -name '*.json' \
-not -path './node_modules/*' \
-not -path './dist/*' \
-print0 | xargs -0 -n1 jq empty
-print0 và xargs -0 xử lý dấu cách trong tên tệp, còn -n1 đảm bảo một tệp mỗi lần gọi để thông báo lỗi chỉ ra thủ phạm. Đừng đưa hàng trăm tệp cho một tiến trình; các thông báo lỗi sẽ đè lên nhau.
jsonlint là lựa chọn phổ biến khác, thường được gọi là npx jsonlint -q data.json, trong đó -q chỉ in lỗi. Nó báo số dòng và cột, nhưng mức bảo trì kém tích cực hơn jq, và các phiên bản khác nhau dung sai BOM và comment khác nhau. Hãy coi nó là một trình kiểm tra cú pháp dễ đọc hơn, không phải một trình kiểm chứng schema.
3. Lớp schema: những điều cơ bản của ajv-cli validate
Ajv là trình kiểm chứng schema được dùng rộng nhất trong hệ sinh thái JavaScript, và ajv-cli phơi bày nó như một công cụ dòng lệnh:
ajv validate -s schema.json -d data.json
-s trỏ tới tệp schema và -d tới dữ liệu được kiểm chứng. Khi thất bại, tiến trình thoát khác 0, nên CI thất bại ngay. Dự án thật thường thêm hai cờ:
npx ajv-cli validate \
-s schemas/order.json \
-d 'fixtures/orders/*.json' \
--strict=false \
--errors=text
--strict=false giúp với schema cũ: chế độ strict coi từ khóa lạ và những khai báo required đáng ngờ là lỗi, điều mà nhiều schema kế thừa trượt. --errors=text tạo đầu ra người đọc được, còn cấu trúc JSON mặc định tốt hơn cho máy. Hãy chọn dựa trên ai tiêu thụ kết quả.
Phiên bản draft của schema cũng phải khớp. Các phiên bản major của Ajv mặc định hỗ trợ draft khác nhau, và ajv-cli có thể chọn bằng --spec=draft7, --spec=draft2019 hoặc --spec=draft2020. Nếu schema khai báo $schema: "https://json-schema.org/draft/2020-12/schema" nhưng CLI biên dịch nó như draft-07, một số từ khóa bị bỏ qua một cách âm thầm. Để biết thêm về viết schema và khác biệt phiên bản, xem Bắt đầu với JSON Schema.
Kiểm chứng định dạng là một yêu cầu thường gặp khác. Từ khóa format mặc định chỉ là annotation; để email, date-time và uri thực sự ràng buộc điều gì đó, hãy nạp ajv-formats:
npx ajv-cli validate -c ajv-formats -s schema.json -d data.json
Bước này dễ bị bỏ sót, và kết quả là trường email chấp nhận not-an-email. Trước khi phát hành, hãy đưa vào pipeline một bản ghi cố tình không hợp lệ; điều đó chứng minh quy tắc đang hoạt động tốt hơn nhiều so với đọc tài liệu mười lần.
check-jsonschema: nhiều định dạng và lỗi thân thiện hơn
Nếu nhóm xử lý YAML, TOML hay cấu hình GitHub Actions bên cạnh JSON, check-jsonschema tiện hơn:
check-jsonschema --schemafile schemas/config.json config/app.json
check-jsonschema --schemafile schemas/pipeline.json .github/workflows/ci.yml
Lợi thế thực tế của nó:
- Hỗ trợ YAML sẵn, phù hợp với những tệp "hình dạng JSON nhưng viết bằng YAML", như manifest Kubernetes và cấu hình CI.
- Lỗi bao gồm đường dẫn tệp, số dòng và đường dẫn trường, giữ chi phí định vị thấp.
- Có sẵn hook pre-commit, nên tích hợp rẻ.
- Có thể lấy schema phổ biến từ SchemaStore, nhưng cũng chấp nhận tệp cục bộ, tránh phụ thuộc mạng.
Trong nhóm đa ngôn ngữ, Python thường đã có sẵn, nên cài và nâng cấp check-jsonschema nhẹ hơn duy trì toolchain Node. Để jq lo cú pháp và công cụ này lo cấu trúc; tổ hợp có chi phí bảo trì rất thấp.
4. Mã thoát là tín hiệu CI duy nhất đáng tin
Đầu ra bóng bẩy đến đâu, CI chỉ đọc mã thoát. Ba bẫy phổ biến:
| Triệu chứng | Nguyên nhân | Cách sửa |
|---|---|---|
| Có lỗi nhưng CI xanh | Lệnh kết thúc bằng ` | |
| Không lỗi nhưng CI đỏ | diff coi "tìm thấy khác biệt" là thất bại |
Phân biệt "khác biệt" với "lỗi thực thi" |
| Đầu ra lỗi bị cắt | Đầu ra song song nhiều tệp đè lên nhau | Chạy tuần tự hoặc in theo từng tệp |
Hãy nhớ ngữ nghĩa mã thoát của jq: phân tích bình thường trả 0; với -e, kết quả biểu thức false hoặc null trả 1; lỗi cách dùng và tệp thiếu trả 2; lỗi biên dịch script trả 3. Các công cụ schema thường dùng 0 cho thành công, 1 cho thất bại kiểm chứng, và 2 cho lỗi công cụ hoặc tham số. Khi viết wrapper, đừng gộp mọi mã khác 0 thành "dữ liệu sai", nếu không lỗi môi trường sẽ bị chẩn đoán nhầm thành vấn đề nội dung.
5. pre-commit: bắt vấn đề trước khi commit
Giá trị của pre-commit không phải "thêm một lần kiểm tra"; mà là cho nhà phát triển phản hồi trong một phút thay vì sau mười phút chạy CI. Một cấu hình tối thiểu trông như sau:
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$
Hai chi tiết quan trọng:
fileschỉ nên khớp các thư mục cần kiểm chứng. Đừng chạy hook trênnode_moduleshay tệp sinh ra.- Hook cục bộ giả định
jqđã cài trên máy nhà phát triển. Hãy ghi lại yêu cầu đó, hoặc chuyển sang hook trong container.
Nếu dự án đã dùng Husky và lint-staged, hãy kết hợp trực tiếp:
{
"lint-staged": {
"configs/*.json": ["jq empty", "prettier --check"]
}
}
Nhớ rằng lint-staged truyền một danh sách tệp đã stage, nên lệnh phải chấp nhận nhiều đường dẫn. Cả jq empty và prettier --check đều chấp nhận. Đừng để hook quét toàn bộ kho mã; commit sẽ chậm đi rõ rệt.
6. GitHub Actions: một pipeline bạn có thể sao chép
Workflow dưới đây thực hiện kiểm chứng cú pháp, kiểm chứng schema, và giữ báo cáo khi thất bại:
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
Bộ lọc paths ngăn những thay đổi chỉ liên quan tài liệu kích hoạt kiểm chứng. npx --yes phù hợp với kho mã không ghim phụ thuộc; dự án production nên thêm ajv-cli vào devDependencies và ghim phiên bản. Không có lock, một bản phát hành thượng nguồn thay đổi draft mặc định hoặc chế độ strict có thể khiến pipeline đỏ qua đêm.
7. Định vị lỗi: từ số dòng đến đường dẫn trường
Một nửa giá trị của validator là phát hiện; nửa còn lại là định vị. Hãy học đọc ba loại đầu ra:
- Lỗi cú pháp:
jqbáo dòng và cột, thường kèm một đoạn gần đó. Hãy sửa ngoặc và dấu phẩy trước; đừng nghi ngờ mã hoá ngay. - Lỗi schema:
instancePathcủa Ajv chỉ vị trí dữ liệu (ví dụ/items/0/price),schemaPathchỉ quy tắc (ví dụ#/properties/items/items/properties/price/type), vàkeywordnêu ràng buộc bị vi phạm. - Lỗi định dạng: khi kiểm tra
formatthất bại, thông báo thường chỉ nói "must match format "email"". Hãy kết hợp nó vớiinstancePathđể xác định trường.
Dán instancePath vào hồ sơ điều tra; nó hữu ích hơn nhiều so với một bức tường JSON. Với mảng, chỉ số số cũng cho bạn biết bản ghi nào thất bại. Nếu khóa nghiệp vụ trực quan hơn, hãy thêm một dòng ánh xạ sau đó, như items[0].id = ORD-042.
Các lỗi schema phổ biến, như lạm dụng additionalProperties: false hay viết các nhánh oneOf thực ra chồng lên nhau, được đề cập trong Những bẫy phổ biến khi sinh JSON Schema. Một quy tắc kiểm chứng sai còn nguy hiểm hơn không kiểm chứng, vì nó tạo cảm giác an toàn giả.
8. Bảng chọn công cụ
| Nhu cầu | Công cụ đề xuất | Vì sao | Cần chú ý |
|---|---|---|---|
| Chỉ cú pháp | jq empty |
Không phụ thuộc, nhanh, mã thoát rõ ràng | Dấu cách trong tên tệp khi chạy hàng loạt |
| Lỗi dòng/cột thân thiện hơn | jsonlint |
Đầu ra dễ đọc | Mức độ bảo trì hạn chế |
| Kiểm chứng JSON Schema | ajv-cli |
Hệ sinh thái trưởng thành, lỗi có cấu trúc | Khớp phiên bản draft và nạp hỗ trợ format |
| Kho mã lẫn JSON và YAML | check-jsonschema |
Đa định dạng, thân thiện với pre-commit | Nguồn schema và truy cập mạng |
| Phản hồi nhanh trước commit | pre-commit / lint-staged | Chỉ xử lý tệp đã stage | Hook phải chấp nhận nhiều đường dẫn |
| Định dạng nhất quán | Prettier | Được dùng rộng rãi | Dùng --check khi bạn không muốn ghi lại tệp |
Một khuyến nghị cuối: hãy xác minh pipeline thực sự thất bại bằng cách đưa vào một bản ghi cố tình không hợp lệ trước. Rủi ro lớn nhất không phải cấu hình phức tạp; mà là một cấu hình không bao giờ có hiệu lực. Coi "nó có thể thất bại" là tiêu chí chấp nhận đáng tin hơn coi "nó qua" là tiêu chí. Với chuỗi công cụ dòng lệnh rộng hơn, tiếp tục với Trình soạn thảo, dòng lệnh và tự động hoá.
FAQ
Nên dùng jq empty hay jsonlint?
Hãy ưu tiên jq empty trong CI: không phụ thuộc, khởi động nhanh, ngữ nghĩa mã thoát rõ ràng. Dùng jsonlint khi bạn cần thông báo dòng/cột thân thiện hơn hoặc đang điều tra cục bộ. Cả hai chỉ thực hiện kiểm chứng cú pháp; không cái nào kiểm tra cấu trúc trường.
Vì sao ajv báo một từ khóa không xác định?
Trước tiên hãy xác nhận draft $schema của schema khớp với phiên bản CLI dùng. Nhiều lỗi "unknown keyword" là từ khóa của draft mới gặp phải một compiler cũ hơn. Nếu phiên bản đã khớp, hãy kiểm tra lỗi chính tả. --strict=false có thể giữ schema kế thừa hoạt động, nhưng đừng dựa vào nó lâu dài để che các vấn đề thật.
Kiểm chứng schema qua nhưng API vẫn lỗi. Vì sao?
Schema kiểm chứng cấu trúc, không kiểm chứng ngữ nghĩa nghiệp vụ. Ví dụ status có thể là chuỗi với enum hợp lệ, trong khi máy trạng thái cấm chuyển trực tiếp từ paid sang refunded. Những quy tắc như vậy thuộc về mã hoặc ràng buộc cơ sở dữ liệu, không thuộc JSON Schema.
Làm sao định vị nhanh trường bị lỗi?
Hãy đọc instancePath hoặc số dòng từ đầu ra công cụ. instancePath của Ajv là một JSON Pointer trỏ thẳng vào dữ liệu; check-jsonschema báo tệp và dòng. Hãy ghi lại đường dẫn và ánh xạ nó sang khóa nghiệp vụ; việc định vị vấn đề thường mất vài phút.
pre-commit có nên chạy kiểm chứng đầy đủ không?
Không. pre-commit chỉ nên xử lý tệp đã stage để giữ commit nhanh. Việc quét toàn bộ thuộc về CI hoặc job định kỳ. Cách phân chia đơn giản: chạy cục bộ ưu tiên tốc độ, CI ưu tiên tính đầy đủ.