Editor, Command Line และ Toolchain สำหรับ Automation
สรุปสำคัญ: Editor แก้ปัญหา "เห็นชัด แก้ไขถูก" ขณะที่ command line แก้ปัญหา "ทำทีละมาก ๆ และทำซ้ำได้" แต่ละฝ่ายมีเครื่องมือไม่กี่ตัวที่ยังใช้ได้ดีไปอีกทศวรรษ

สรุปสั้น: เลือก editor ตามสถานการณ์ — CodeMirror 6 สำหรับเว็บยูทิลิตี้และมือถือ ใช้ Monaco เฉพาะเมื่อต้องการประสบการณ์ IDE เต็มรูปแบบ; ปล่อยงานแบบทีละมาก ๆ ให้ jq พร้อมการตรวจสามชั้นใน CI สำหรับไวยากรณ์ โครงสร้าง และสไตล์ และส่งอาร์กิวเมนต์ด้วย jq --arg เสมอ
ยุคของการตรวจ JSON ด้วยมือควรจบไปแล้ว: อะไรที่เครื่องทำซ้ำได้ อย่าปล่อยให้ตาคนทำ
1. Editor ใน browser
1.1 การผสาน CodeMirror 6 แบบน้อยที่สุด
import { EditorView, basicSetup } from 'codemirror';
import { json, jsonParseLinter } from '@codemirror/lang-json';
import { linter, lintGutter } from '@codemirror/lint';
const view = new EditorView({
parent: document.getElementById('editor')!,
doc: '{"hello": "json"}',
extensions: [
basicSetup,
json(),
linter(jsonParseLinter()),
lintGutter(),
EditorView.updateListener.of((update) => {
if (update.docChanged) {
// Debounce before doing heavy work; do not format on every keystroke
scheduleParse(update.state.doc.toString());
}
}),
],
});
จุดที่ควรสังเกตสี่ข้อ:
json()ให้การไฮไลต์ไวยากรณ์และการจับคู่วงเล็บ — ไม่ต้องเขียนเอง;jsonParseLinter()สร้าง marker ของ error ระดับบรรทัด/คอลัมน์ ใช้ร่วมกับlintGutter()เพื่อแสดงไอคอนข้างเลขบรรทัด;updateListenerต้องถูก debounce การเรียกJSON.parseกับข้อมูลแสนบรรทัดทุกครั้งที่กดคีย์จะเห็นความหน่วงชัดเจน;- เอกสารขนาดใหญ่ต้องใช้เทคนิคเพิ่มประสิทธิภาพนอกเหนือจาก
EditorView.lineWrappingเพื่อเลี่ยงการ render ทั้งหมดในครั้งเดียว (CodeMirror 6 render ตาม viewport เป็นค่าเริ่มต้น ซึ่งดีกว่าระบบเวอร์ชันเก่า)
1.2 Folding และ outline
CodeMirror 6 มี folding ในตัว ในสถานการณ์ JSON คุณมักพับตาม "object/array":
import { foldGutter, foldKeymap } from '@codemirror/language';
import { keymap } from '@codemirror/view';
ด้วย foldGutter() และ keymap.of(foldKeymap) ผู้ใช้สามารถย่อ array ยาว ๆ ได้ด้วยคีย์ลัด
1.3 เลือกระหว่าง CodeMirror กับ Monaco
| มิติ | CodeMirror 6 | Monaco |
|---|---|---|
| ขนาด bundle | เล็ก แยกโมดูลได้ | ใหญ่ (หลาย MB) |
| มือถือ | รองรับดี | ประสบการณ์ปานกลาง |
| Language service | ต้องสร้างเอง | ครบครัน มีมาให้ |
| ต้นทุนการปรับแต่ง | ต่ำ | ปานกลาง |
เว็บยูทิลิตี้และมือถือเป็นหลัก → CodeMirror; ประสบการณ์ IDE เต็มรูปแบบ (หลายไฟล์, go-to-definition, completion) → Monaco
2. Command line: สูตร jq ความถี่สูงหกสูตร
jq ต่อ JSON เทียบได้กับ sed + awk ต่อข้อความ การเรียนเพียงส่วนเล็กของไวยากรณ์ก็ครอบคลุมความต้องการในชีวิตประจำวันแปดสิบเปอร์เซ็นต์
# 1. Pick fields (the most common)
jq '.data.items[].name' response.json
# 2. Filter + reshape
jq '.data.items[] | select(.price < 10) | {id, price}' response.json
# 3. Aggregates (length, sum, max)
jq '[.items[] | .price] | {count: length, total: add, max: max}' response.json
# 4. Group and aggregate
jq '[.items[]] | group_by(.category) | map({category: .[0].category, count: length})' response.json
# 5. Merge multiple files into an array
jq -s '.' part-*.json > merged.json
# 6. Process JSON Lines line by line
jq -c 'select(.level == "error")' app.jsonl
# Common output switches
jq -r '.data.name' # Strip the quotes so it pipes cleanly into the shell
jq -c '.' # Compact output, one line
jq --arg id "$ID" '.id == $id' # Pass arguments safely, avoiding injection through concatenation
--arg สมควรได้รับการกล่าวถึงเป็นพิเศษ: การต่อตัวแปรเข้าใน expression โดยตรงจะพังทันทีเมื่อค่ามีเครื่องหมายคำพูด ขณะที่ --arg ให้ jq จัดการการ escape เอง
2.1 เครื่องมืออื่นโดยสรุป
| เครื่องมือ | จุดประสงค์ | ในหนึ่งประโยค |
|---|---|---|
yq |
แปลง YAML/JSON/XML | ไวยากรณ์คล้าย jq เป็นม้าทำงานหนักของไฟล์กำหนดค่า |
dasel |
query ข้ามรูปแบบ | รองรับ JSON/YAML/TOML/XML/CSV |
fx |
เรียกดู JSON แบบโต้ตอบ | คลิกไปมาใน terminal เหมาะกับการสำรวจ |
jless |
เรียกดูแบบแบ่งหน้าใน terminal | เหมือน less มี folding และการค้นหา |
gron |
แบน JSON เป็นบรรทัดที่ grep ได้ | เครื่องมือเยี่ยมสำหรับหาว่า "ฟิลด์นี้อยู่ระดับไหน" |
ajv-cli |
ตรวจ schema | ตรวจไฟล์ JSON ใน CI |
แนวคิดเบื้องหลัง gron น่าสนใจเป็นพิเศษ:
$ echo '{"a":{"b":[1,2]}}' | gron
json = {};
json.a = {};
json.a.b = [];
json.a.b[0] = 1;
json.a.b[1] = 2;
เมื่อแบนแล้ว grep สามารถระบุ path ได้ ซึ่งเร็วกว่าการขยายทีละระดับใน editor มาก
3. การตรวจสามชั้นใน CI
| ชั้น | เครื่องมือ | ความล้มเหลวหมายถึงอะไร |
|---|---|---|
| ไวยากรณ์ | JSON.parse / jq empty |
ไฟล์เองผิดรูป |
| โครงสร้าง | ajv-cli + schema |
ฟิลด์หาย, ชนิดผิด |
| สไตล์ | Prettier | การจัดรูปแบบไม่สม่ำเสมอ, diff มีเสียงรบกวน |
# Syntax: batch-validate every json file in the repository
find . -name '*.json' -not -path './node_modules/*' -print0 \
| xargs -0 -n1 jq empty
# Structure: validate against a schema
npx ajv-cli validate -s schemas/order.json -d 'fixtures/orders/*.json' --strict=false
# Style: check only, do not modify
npx prettier --check "**/*.json"
คุณต้องมีทั้งสามชั้น เพราะข้อความ error ของแต่ละชั้นแตกต่างกันโดยสิ้นเชิง:
jq empty บอกเพียง "บรรทัดไหนเสีย", ajv บอก "ฟิลด์ไหนละเมิด keyword ไหน" และ Prettier บอกเพียง "การจัดรูปแบบไม่สม่ำเสมอ"
4. pre-commit hook
# .husky/pre-commit example (excerpt)
npx lint-staged
{
"lint-staged": {
"*.json": ["prettier --write", "jq empty"]
}
}
ระวังไฟล์กำหนดค่าของเครื่องมืออย่าง package.json และ tsconfig.json อย่าให้ Prettier จัดย่อหน้าใหม่ตามใจ —
เครื่องมือบางตัวไวต่อการจัดรูปแบบ (JSON ที่ถูกต้องไม่ควรเป็นเช่นนั้น แต่ในทางปฏิบัติเคยเกิดปัญหา) คุณสามารถยกเว้นทีละไฟล์ได้
5. เคล็ดลับการ debug
- ใช้
jq -eเป็น assertion: exit code เป็น 1 เมื่อผลลัพธ์ของ expression เป็นfalseหรือnullจึงใส่ในสคริปต์เป็นการตรวจได้ทันที; jq 'paths'แสดง path ทั้งหมด ให้ภาพรวมอย่างเร็วของโครงสร้างเอกสารที่ไม่คุ้น;jq '.. | objects | keys'ค้นหาชื่อคีย์ทุกชื่อที่เคยปรากฏ เหมาะกับการตามล่าการเขียนที่ผิดรูปแบบ;- จัดรูปแบบก่อนเปรียบเทียบใน editor: diff ที่ไม่จัดรูปแบบคือเสียงรบกวนล้วน ๆ การเปรียบเทียบเชิงโครงสร้างอย่าง
/diffแม่นยำกว่า
6. รายการตรวจสอบ
- [ ] marker ของ error ใน editor แม่นยำถึงบรรทัดและคอลัมน์ และการ parse ถูก debounce;
- [ ] สคริปต์ของทีมส่งอาร์กิวเมนต์ด้วย
jq --argแทนการต่อสตริง; - [ ] CI มีการตรวจครบทั้งสามชั้น (ไวยากรณ์/โครงสร้าง/สไตล์);
- [ ] pre-commit ประมวลผลเฉพาะไฟล์ที่ stage แล้ว การสแกนทั้งหมดจึงไม่ทำให้ commit ช้าลง;
- [ ] เวอร์ชันของเครื่องมือถูกปักหมุดใน
package.jsonหรือ.tool-versions
คำถามที่พบบ่อย
จะเลือกระหว่าง CodeMirror 6 กับ Monaco อย่างไร?
สำหรับเว็บยูทิลิตี้และมือถือ ให้เลือก CodeMirror 6: มันเล็ก แยกโมดูลได้ รองรับมือถือดี และการไฮไลต์ไวยากรณ์ การจับคู่วงเล็บ และ folding สามารถประกอบตามต้องการ ใช้ Monaco เฉพาะเมื่อต้องการประสบการณ์ IDE เต็มรูปแบบที่มีหลายไฟล์ go-to-definition และ completion — โดยมีต้นทุนเป็น bundle หลายเมกะไบต์และความพยายามในการปรับแต่งที่สูงขึ้น
ทำไม CI ต้องมีการตรวจไวยากรณ์ โครงสร้าง และสไตล์พร้อมกัน?
ทั้งสามชั้นรายงาน error ที่แตกต่างกันโดยสิ้นเชิง: jq empty บอกว่าบรรทัดไหนเสีย, ajv บอกว่าฟิลด์ไหนละเมิด keyword ไหน และ Prettier บอกเพียงว่าการจัดรูปแบบไม่สม่ำเสมอ การตัดชั้นใดออกจะทำให้ error ของชั้นอื่นถูกอ่านผิดได้ง่าย จึงควรเก็บทั้งสามชั้นและหยุดทันทีเมื่อชั้นใดล้มเหลว
ควรพึ่ง pre-commit แทนการตรวจใน CI หรือไม่?
ไม่ Hook ประมวลผลเฉพาะไฟล์ที่ stage ซึ่งให้ feedback ภายในหนึ่งนาที แต่ครอบคลุมทั้ง repository ไม่ได้ และครอบคลุมการเปลี่ยนแปลงที่ข้าม commit ไม่ได้ CI รับผิดชอบความครบถ้วน: เก็บ pre-commit ไว้บนไฟล์ที่ stage และปล่อยการสแกนทั้งหมดให้ CI