การตรวจสอบ JSON ใน CI: pipeline ด้วย jsonlint, ajv และ jq
สรุปสำคัญ: การตรวจไวยากรณ์พิสูจน์ว่าไฟล์ parse ได้ ส่วนการตรวจ schema พิสูจน์ว่าฟิลด์เป็นไปตามข้อตกลง ใน CI สิ่งที่สำคัญคือ exit code ที่ไม่กำกวม ตำแหน่ง error และเส้นทางการซ่อม

สรุปสั้น: ใน CI ให้ jq empty จัดการไวยากรณ์, ajv-cli จัดการโครงสร้าง (ใช้ check-jsonschema สำหรับทีมที่ใช้หลายรูปแบบ) และ Prettier จัดการสไตล์; เชื่อมต่อเป็นชั้น ๆ และหยุดที่ความล้มเหลวแรก
ผ่าน parser ใน editor ในเครื่องคุณ ไม่ได้หมายความว่าไฟล์นั้นควรอยู่ใน CI; ผ่าน CI ก็ไม่ได้หมายความว่าฟิลด์ตรงกับข้อตกลงเช่นกัน
1. แยกการตรวจไวยากรณ์ออกจากการตรวจ schema
คำว่า "การตรวจสอบ JSON" ถูกใช้กว้างเกินไปมาก จึงเป็นเหตุให้ทีมมักเลือกเครื่องมือเดียวเพื่อแก้ปัญหาสองอย่างที่ต่างกัน:
| ชั้น | ตอบคำถามอะไร | เครื่องมือทั่วไป | ความล้มเหลวหมายถึงอะไร |
|---|---|---|---|
| ไวยากรณ์ | ไฟล์นี้เป็น JSON ที่ถูกต้องหรือไม่? | jq empty, jsonlint, python -m json.tool |
วงเล็บ, เครื่องหมายจุลภาค, เครื่องหมายคำพูด หรือการเข้ารหัสเสีย |
| Schema | ฟิลด์, ชนิด และคุณสมบัติที่จำเป็นตรงกับข้อตกลงหรือไม่? | ajv-cli, check-jsonschema |
ไฟล์ parse ได้ แต่โครงสร้างธุรกิจผิด |
| สไตล์ | การย่อหน้าและลำดับคีย์สม่ำเสมอหรือไม่? | Prettier, jq -S |
การจัดรูปแบบไม่สม่ำเสมอและ diff มีเสียงรบกวน |
ชั้นเหล่านี้แทนกันไม่ได้ ไฟล์ที่ผ่านไวยากรณ์ยังล้มเหลว schema ได้: {"price": "12.5"} เป็น JSON ที่ถูกต้อง แต่ถ้า schema กำหนดให้ price เป็นจำนวน มันก็ไม่ผ่าน ในทางกลับกัน ไม่ว่า schema จะครบถ้วนเพียงใด ไฟล์ที่ขาดวงเล็บปิดก็จะไม่มีทาง compile กับมันได้
ดังนั้น pipeline ควรตรวจไวยากรณ์ก่อน โครงสร้างที่สอง และสไตล์สุดท้าย หยุดทันทีเมื่อชั้นใดล้มเหลว ไม่อย่างนั้น parse error จะปลอมตัวเป็น field error
2. ชั้นไวยากรณ์: jq empty เป็นตัวเลือกที่ dependency น้อยที่สุด
หาก CI image มี jq อยู่แล้ว การตรวจไวยากรณ์แทบไม่มีค่าใช้จ่าย:
jq empty data.json
jq empty ไม่สร้างเอาต์พุต มันเพียง parse ไฟล์ ออกด้วย 0 เมื่อสำเร็จ และออกด้วยรหัสที่ไม่เป็นศูนย์พร้อมข้อมูลบรรทัด/คอลัมน์เมื่อล้มเหลว ทำให้เบากว่าการดึง dependency tree ของ Node ทั้งชุด และเป็นด่านแรกที่ดี
เมื่อตรวจไฟล์ JSON แบบทีละมากใน repository ให้ยกเว้น node_modules, เอาต์พุต build และแคชของบุคคลที่สาม:
find . -name '*.json' \
-not -path './node_modules/*' \
-not -path './dist/*' \
-print0 | xargs -0 -n1 jq empty
-print0 และ xargs -0 จัดการช่องว่างในชื่อไฟล์ ขณะที่ -n1 รับประกันหนึ่งไฟล์ต่อการเรียกหนึ่งครั้ง เพื่อให้ข้อความ error ระบุตัวผู้กระทำผิดได้ อย่าส่งไฟล์หลายร้อยไฟล์ให้โปรเซสเดียว เพราะข้อความ error จะเขียนทับกัน
jsonlint เป็นอีกตัวเลือกที่พบบ่อย มักเรียกเป็น npx jsonlint -q data.json โดย -q พิมพ์เฉพาะ error มันรายงานเลขบรรทัดและคอลัมน์ แต่ได้รับการดูแลน้อยกว่า jq และแต่ละเวอร์ชันยอมรับ BOM และ comment ต่างกัน ให้ถือเป็นตัวตรวจไวยากรณ์ที่อ่านง่ายกว่า ไม่ใช่ schema validator
3. ชั้น schema: พื้นฐานของ ajv-cli validate
Ajv เป็น schema validator ที่ใช้กันแพร่หลายที่สุดในระบบนิเวศ JavaScript และ ajv-cli เปิดเผยมันเป็นเครื่องมือ command line:
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 ถือ keyword ที่ไม่รู้จักและการประกาศ required ที่น่าสงสัยเป็น error ซึ่ง schema ที่สืบทอดมาหลายตัวล้มเหลว --errors=text ให้เอาต์พุตที่มนุษย์อ่านได้ ขณะที่โครงสร้าง JSON ค่าเริ่มต้นเหมาะกับเครื่อง เลือกตามผู้บริโภคผลลัพธ์
เวอร์ชัน draft ของ schema ก็ต้องตรงกันด้วย Ajv แต่ละ major version รองรับ draft ที่ต่างกันเป็นค่าเริ่มต้น และ ajv-cli เลือกได้ด้วย --spec=draft7, --spec=draft2019 หรือ --spec=draft2020 หาก schema ประกาศ $schema: "https://json-schema.org/draft/2020-12/schema" แต่ CLI compile เป็น draft-07 keyword บางตัวจะถูกเพิกเฉยอย่างเงียบ ๆ สำหรับเรื่องการเขียน schema และความแตกต่างของเวอร์ชันเพิ่มเติม ดู เริ่มต้นกับ JSON Schema
การตรวจรูปแบบเป็นความต้องการที่พบบ่อยอีกอย่าง keyword format เป็นเพียง annotation โดยค่าเริ่มต้น การจะให้ email, date-time และ uri บังคับใช้จริง ต้องโหลด ajv-formats:
npx ajv-cli validate -c ajv-formats -s schema.json -d data.json
ขั้นตอนนี้พลาดง่าย และผลคือฟิลด์อีเมลยอมรับ not-an-email ก่อนส่งมอบ ให้ป้อนเรกคอร์ดที่ตั้งใจทำให้ผิดหนึ่งรายการเข้าสู่ pipeline ซึ่งพิสูจน์ได้ว่ากฎทำงานอยู่ดีกว่าการอ่านเอกสารสิบครั้ง
check-jsonschema: หลายรูปแบบและ error ที่เป็นมิตรกว่า
หากทีมจัดการ YAML, TOML หรือ configuration ของ GitHub Actions นอกเหนือจาก JSON, check-jsonschema สะดวกกว่า:
check-jsonschema --schemafile schemas/config.json config/app.json
check-jsonschema --schemafile schemas/pipeline.json .github/workflows/ci.yml
ข้อดีในทางปฏิบัติ:
- รองรับ YAML โดยธรรมชาติ เหมาะกับไฟล์ที่ "มีรูปร่างเป็น JSON แต่เขียนด้วย YAML" เช่น Kubernetes manifest และ CI configuration
- Error ระบุ path ของไฟล์ เลขบรรทัด และ path ของฟิลด์ ทำให้ต้นทุนการระบุตำแหน่งต่ำ
- มี pre-commit hook ให้ การผสานจึงมีต้นทุนต่ำ
- ดึง schema ที่พบบ่อยจาก SchemaStore ได้ แต่รับไฟล์ในเครื่องได้ด้วย จึงเลี่ยงการพึ่งพาเครือข่าย
ในทีมข้ามภาษา Python มักมีอยู่แล้ว การติดตั้งและอัปเกรด check-jsonschema จึงเบากว่าการดูแล toolchain ของ Node ให้ jq จัดการไวยากรณ์และเครื่องมือนี้จัดการโครงสร้าง ชุดค่าผสมนี้มีภาระในการดูแลต่ำมาก
4. Exit code คือสัญญาณเดียวที่ CI เชื่อถือได้
ไม่ว่าเอาต์พุตจะสวยงามเพียงใด CI อ่านเพียง exit code มีกับดักที่พบบ่อยสามข้อ:
| อาการ | สาเหตุ | วิธีแก้ |
|---|---|---|
| มี error แต่ CI เขียว | คำสั่งลงท้ายด้วย ` | |
| ไม่มี error แต่ CI แดง | diff ถือ "พบความแตกต่าง" เป็นความล้มเหลว |
แยก "ความแตกต่าง" ออกจาก "error ในการรัน" |
| เอาต์พุต error ถูกตัด | เอาต์พุตหลายไฟล์แบบขนานเขียนทับกัน | รันแบบอนุกรมหรือพิมพ์ทีละไฟล์ |
จำความหมาย exit code ของ jq ไว้: parse ปกติคืน 0; ด้วย -e ผลลัพธ์ expression ที่เป็น false หรือ null คืน 1; error การใช้งานและไฟล์ที่หายไปคืน 2; error compile สคริปต์คืน 3 เครื่องมือ schema โดยทั่วไปใช้ 0 สำหรับสำเร็จ, 1 สำหรับการตรวจไม่ผ่าน และ 2 สำหรับ error ของเครื่องมือหรืออาร์กิวเมนต์ เมื่อเขียน wrapper อย่ายุบรหัสที่ไม่เป็นศูนย์ทุกตัวเป็น "ข้อมูลผิด" ไม่อย่างนั้นความล้มเหลวของสภาพแวดล้อมจะถูกวินิจฉัยผิดว่าเป็นปัญหาของเนื้อหา
5. pre-commit: จับปัญหาก่อน commit
คุณค่าของ pre-commit ไม่ใช่ "การตรวจเพิ่มอีกหนึ่งอย่าง" แต่คือการให้ feedback กับนักพัฒนาภายในหนึ่งนาทีแทนที่จะรอ CI รันสิบนาที configuration ขั้นต่ำมีลักษณะดังนี้:
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ควรจับเฉพาะไดเรกทอรีที่ต้องตรวจ อย่ารัน hook กับnode_modulesหรือไฟล์ที่สร้างขึ้น- hook แบบ local สมมติว่าเครื่องนักพัฒนาติดตั้ง
jqไว้ ให้เขียนข้อกำหนดนั้นในเอกสาร หรือเปลี่ยนเป็น hook แบบ container
หากโปรเจกต์ใช้ Husky และ lint-staged อยู่แล้ว ให้ผสานโดยตรง:
{
"lint-staged": {
"configs/*.json": ["jq empty", "prettier --check"]
}
}
จำไว้ว่า lint-staged ส่งรายการไฟล์ที่ stage แล้ว คำสั่งจึงต้องรับหลาย path ได้ ทั้ง jq empty และ prettier --check ทำได้ อย่าให้ hook สแกนทั้ง repository เพราะ commit จะช้าลงอย่างเห็นได้ชัด
6. GitHub Actions: pipeline ที่คัดลอกได้
workflow ด้านล่างทำการตรวจไวยากรณ์ ตรวจ schema และเก็บ report ไว้เมื่อล้มเหลว:
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 เหมาะกับ repository ที่ไม่ได้ปักหมุด dependency ส่วนโปรเจกต์ production ควรเพิ่ม ajv-cli ใน devDependencies และปักหมุดเวอร์ชัน หากไม่มี lock การออกเวอร์ชันใหม่ upstream ที่เปลี่ยน draft หรือ strict mode ค่าเริ่มต้นสามารถทำให้ pipeline แดงข้ามคืนได้
7. ตำแหน่ง error: จากเลขบรรทัดถึง field path
ครึ่งหนึ่งของคุณค่าของ validator คือการตรวจจับ อีกครึ่งคือการระบุตำแหน่ง ให้เรียนรู้อ่านเอาต์พุตสามแบบ:
- Error ไวยากรณ์:
jqรายงานบรรทัดและคอลัมน์ มักพร้อมส่วนข้อความใกล้เคียง ให้ซ่อมวงเล็บและเครื่องหมายจุลภาคก่อน อย่าสงสัยการเข้ารหัสทันที - Error ของ schema:
instancePathของ Ajv ชี้ตำแหน่งข้อมูล (เช่น/items/0/price),schemaPathชี้กฎ (เช่น#/properties/items/items/properties/price/type) และkeywordบอกชื่อข้อจำกัดที่ถูกละเมิด - Error รูปแบบ: เมื่อการตรวจ
formatล้มเหลว ข้อความมักบอกเพียง "must match format "email"" ให้รวมกับinstancePathเพื่อระบุฟิลด์
วาง instancePath ลงในบันทึกการสอบสวน มันมีประโยชน์กว่ากำแพง JSON มาก สำหรับ array index ตัวเลขยังบอกว่าเรกคอร์ดไหนล้มเหลว หากคีย์ธุรกิจเข้าใจง่ายกว่า ให้เพิ่มบรรทัด mapping ภายหลัง เช่น items[0].id = ORD-042
ข้อผิดพลาด schema ที่พบบ่อย เช่นการใช้ additionalProperties: false มากเกินไป หรือเขียนสาขา oneOf ที่ทับซ้อนกันจริง ๆ มีกล่าวถึงใน ข้อผิดพลาดทั่วไปในการสร้าง JSON Schema กฎการตรวจที่ผิดอันตรายกว่าการไม่ตรวจ เพราะมันสร้างความรู้สึกปลอดภัยที่ผิด
8. ตารางเลือกเครื่องมือ
| ความต้องการ | เครื่องมือที่แนะนำ | ทำไม | ข้อควรระวัง |
|---|---|---|---|
| ไวยากรณ์เท่านั้น | jq empty |
ไม่มี dependency, เร็ว, exit code ชัดเจน | ช่องว่างในชื่อไฟล์เมื่อรันเป็นชุด |
| Error บรรทัด/คอลัมน์ที่เป็นมิตรกว่า | jsonlint |
เอาต์พุตอ่านง่าย | การดูแลรักษามีจำกัด |
| ตรวจ JSON Schema | ajv-cli |
ระบบนิเวศที่เติบโตเต็มที่, error มีโครงสร้าง | จัด draft ให้ตรงและโหลดการรองรับ format |
| repository ผสม JSON และ YAML | check-jsonschema |
หลายรูปแบบ, เป็นมิตรกับ pre-commit | แหล่ง schema และการเข้าถึงเครือข่าย |
| Feedback เร็ว ก่อน commit | pre-commit / lint-staged | ประมวลผลเฉพาะไฟล์ที่ stage | Hook ต้องรับหลาย path |
| การจัดรูปแบบสม่ำเสมอ | Prettier | ถูกนำไปใช้กว้างขวาง | ใช้ --check เมื่อไม่อยากเขียนไฟล์ใหม่ |
ข้อแนะนำสุดท้าย: ยืนยันว่า pipeline ล้มเหลวได้จริงด้วยการป้อนเรกคอร์ดที่ตั้งใจทำให้ผิดหนึ่งรายการก่อน ความเสี่ยงใหญ่ที่สุดไม่ใช่ configuration ที่ซับซ้อน แต่คือ configuration ที่ไม่เคยมีผลจริง การถือ "มันล้มเหลวได้" เป็นเกณฑ์ยอมรับเชื่อถือได้มากกว่าการถือ "มันผ่าน" สำหรับ toolchain command line ที่กว้างกว่า ให้อ่านต่อที่ Editor, command line และ automation
คำถามที่พบบ่อย
ควรใช้ jq empty หรือ jsonlint?
ใน CI ให้เลือก jq empty: ไม่มี dependency, เริ่มทำงานเร็ว และความหมายของ exit code ชัดเจน ใช้ jsonlint เมื่อต้องการข้อความบรรทัด/คอลัมน์ที่เป็นมิตรกว่าหรือกำลังสอบสวนในเครื่อง ทั้งสองทำการตรวจไวยากรณ์เท่านั้น ไม่ได้ตรวจโครงสร้างฟิลด์
ทำไม ajv รายงาน unknown keyword?
ก่อนอื่นยืนยันว่า draft ของ $schema ใน schema ตรงกับเวอร์ชันที่ CLI ใช้ error "unknown keyword" หลายรายการเป็น keyword ของ draft ใหม่ที่ compiler รุ่นเก่าเจอ หากเวอร์ชันตรงกัน ให้ตรวจว่าพิมพ์ผิดหรือไม่ --strict=false ทำให้ schema ที่สืบทอดมาทำงานต่อได้ แต่อย่าพึ่งมันในระยะยาวเพื่อปกปิดปัญหาจริง
การตรวจ schema ผ่าน แต่ API ยังล้มเหลว ทำไม?
Schema ตรวจโครงสร้าง ไม่ได้ตรวจความหมายทางธุรกิจ ตัวอย่างเช่น status อาจเป็นสตริงที่มี enum ถูกต้อง ขณะที่ state machine ห้ามเปลี่ยนจาก paid ไป refunded โดยตรง กฎแบบนั้นอยู่ในโค้ดหรือข้อจำกัดของฐานข้อมูล ไม่ใช่ใน JSON Schema
จะระบุฟิลด์ที่ล้มเหลวอย่างรวดเร็วได้อย่างไร?
อ่าน instancePath หรือเลขบรรทัดจากเอาต์พุตของเครื่องมือ instancePath ของ Ajv เป็น JSON Pointer ที่ชี้ข้อมูลโดยตรง; check-jsonschema รายงานไฟล์และบรรทัด บันทึก path แล้ว map กับคีย์ธุรกิจ การระบุปัญหามักใช้เวลาไม่กี่นาที
pre-commit ควรตรวจแบบเต็มหรือไม่?
ไม่ pre-commit ควรประมวลผลเฉพาะไฟล์ที่ stage เพื่อให้ commit เร็ว การสแกนทั้งหมดเป็นของ CI หรือ scheduled job การแบ่งหน้าที่ง่ายมาก: การรันในเครื่องเน้นความเร็ว CI เน้นความครบถ้วน