Trình soạn thảo, dòng lệnh và chuỗi công cụ tự động hoá
Kết luận: Trình soạn thảo giải quyết "nhìn rõ, sửa đúng", còn dòng lệnh giải quyết "hàng loạt, lặp lại được". Mỗi bên có vài công cụ vẫn hữu ích suốt một thập kỷ.

Kết luận: chọn trình soạn thảo theo tình huống — CodeMirror 6 cho trang tiện ích và di động, Monaco chỉ khi bạn cần trải nghiệm IDE trọn vẹn; để việc hàng loạt cho jq cộng ba lớp kiểm tra CI về cú pháp, cấu trúc và phong cách, và luôn truyền tham số bằng jq --arg.
Kỷ nguyên kiểm tra JSON bằng tay nên kết thúc: bất cứ thứ gì máy có thể lặp lại, đừng để mắt người làm.
1. Trình soạn thảo trong trình duyệt
1.1 Tích hợp CodeMirror 6 tối thiểu
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());
}
}),
],
});
Bốn điểm cần lưu ý:
json()cung cấp highlight cú pháp và ghép ngoặc — đừng tự viết;jsonParseLinter()tạo marker lỗi ở mức dòng/cột; hãy ghép nó vớilintGutter()để hiển thị icon cạnh số dòng;updateListenerphải được debounce; gọiJSON.parsetrên trăm nghìn dòng ở mỗi lần gõ sẽ thấy rõ độ trễ;- Tài liệu lớn cần tối ưu ngoài
EditorView.lineWrappingđể tránh render toàn bộ cùng lúc (CodeMirror 6 mặc định render theo viewport, đây là cải thiện so với các bản cũ).
1.2 Folding và outline
CodeMirror 6 có folding sẵn; trong kịch bản JSON bạn thường fold theo "object/array":
import { foldGutter, foldKeymap } from '@codemirror/language';
import { keymap } from '@codemirror/view';
Với foldGutter() và keymap.of(foldKeymap), người dùng có thể thu gọn mảng dài bằng phím tắt.
1.3 Chọn giữa CodeMirror và Monaco
| Khía cạnh | CodeMirror 6 | Monaco |
|---|---|---|
| Kích thước bundle | Nhỏ, dạng module | Lớn (vài MB) |
| Di động | Hỗ trợ tốt | Trải nghiệm trung bình |
| Dịch vụ ngôn ngữ | Phải tự dựng | Phong phú, có sẵn |
| Chi phí tuỳ biến | Thấp | Trung bình |
Trang tiện ích và ưu tiên di động → CodeMirror; trải nghiệm IDE trọn vẹn (nhiều tệp, go-to-definition, completion) → Monaco.
2. Dòng lệnh: sáu công thức jq tần suất cao
jq với JSON giống như sed + awk với văn bản; chỉ học một phần nhỏ cú pháp của nó đã bao phủ tám mươi phần trăm nhu cầu hằng ngày.
# 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 đáng được nhắc riêng: nối biến trực tiếp vào biểu thức sẽ vỡ ngay khi giá trị chứa dấu ngoặc kép, trong khi --arg để jq xử lý escaping.
2.1 Các công cụ khác nhìn nhanh
| Công cụ | Mục đích | Trong một câu |
|---|---|---|
yq |
Chuyển đổi YAML/JSON/XML | Cú pháp giống jq, công cụ chính cho tệp cấu hình |
dasel |
Truy vấn thống nhất qua các định dạng | Hỗ trợ JSON/YAML/TOML/XML/CSV |
fx |
Duyệt JSON tương tác | Bấm quanh trong terminal, tốt cho khám phá |
jless |
Duyệt theo trang trong terminal | Giống less, có folding và tìm kiếm |
gron |
Làm phẳng JSON thành dòng grep được | Công cụ tuyệt vời để biết "trường này ở cấp nào" |
ajv-cli |
Kiểm chứng schema | Kiểm chứng tệp JSON trong CI |
Ý tưởng đằng sau gron đặc biệt đáng biết:
$ echo '{"a":{"b":[1,2]}}' | gron
json = {};
json.a = {};
json.a.b = [];
json.a.b[0] = 1;
json.a.b[1] = 2;
Sau khi làm phẳng, grep có thể định vị đường dẫn, nhanh hơn nhiều so với việc mở rộng từng cấp trong trình soạn thảo.
3. Ba lớp kiểm chứng trong CI
| Lớp | Công cụ | Lỗi nghĩa là gì |
|---|---|---|
| Cú pháp | JSON.parse / jq empty |
Bản thân tệp bị hỏng |
| Cấu trúc | ajv-cli + schema |
Thiếu trường, sai kiểu |
| Phong cách | Prettier | Định dạng không nhất quán, diff nhiễu |
# 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"
Bạn cần cả ba lớp, vì thông báo lỗi của chúng hoàn toàn khác nhau:
jq empty chỉ nói "dòng nào hỏng", ajv nói "trường nào vi phạm từ khóa nào", còn Prettier chỉ nói "định dạng không nhất quán".
4. Hook pre-commit
# .husky/pre-commit example (excerpt)
npx lint-staged
{
"lint-staged": {
"*.json": ["prettier --write", "jq empty"]
}
}
Hãy cẩn thận với các tệp cấu hình công cụ như package.json và tsconfig.json; đừng để Prettier tự ý thụt lề lại chúng —
một số công cụ nhạy cảm với định dạng (JSON hợp lệ lẽ ra không nên như vậy, nhưng trên thực tế đã gây vấn đề). Bạn có thể loại trừ chúng riêng lẻ.
5. Mẹo gỡ lỗi
- Dùng
jq -enhư assertion: mã thoát là 1 khi kết quả biểu thức làfalsehoặcnull, nên có thể đưa thẳng vào script làm kiểm tra; jq 'paths'liệt kê mọi đường dẫn, cho bạn cái nhìn nhanh về cấu trúc của một tài liệu lạ;jq '.. | objects | keys'tìm mọi tên khóa từng xuất hiện, rất tiện để truy lùng cách viết không đồng nhất;- Định dạng trước khi so sánh trong trình soạn thảo: một diff chưa định dạng chỉ là nhiễu; so sánh cấu trúc như
/diffchính xác hơn.
6. Danh sách kiểm tra
- [ ] Marker lỗi của trình soạn thảo chính xác đến dòng và cột, và việc phân tích được debounce;
- [ ] Script của nhóm truyền tham số bằng
jq --argthay vì nối chuỗi; - [ ] CI có đủ ba lớp kiểm chứng (cú pháp/cấu trúc/phong cách);
- [ ] pre-commit chỉ xử lý các tệp đã stage, để việc quét toàn bộ không làm chậm commit;
- [ ] Phiên bản công cụ được ghim trong
package.jsonhoặc.tool-versions.
FAQ
Làm sao chọn giữa CodeMirror 6 và Monaco?
Với trang tiện ích và di động, hãy ưu tiên CodeMirror 6: nó nhỏ, dạng module, hỗ trợ di động tốt, và highlight cú pháp, ghép ngoặc cùng folding có thể tổ hợp theo nhu cầu. Chỉ dùng Monaco khi bạn cần trải nghiệm IDE trọn vẹn với nhiều tệp, go-to-definition và completion — cái giá là bundle vài megabyte và công tuỳ biến cao hơn.
Vì sao CI cần kiểm tra cú pháp, cấu trúc và phong cách cùng lúc?
Ba lớp báo những lỗi hoàn toàn khác nhau: jq empty nói dòng nào hỏng, ajv nói trường nào vi phạm từ khóa nào, còn Prettier chỉ nói định dạng không nhất quán. Bỏ một lớp sẽ khiến lỗi của các lớp còn lại dễ bị đọc sai, nên hãy giữ cả ba và dừng ngay khi một lớp thất bại.
Có nên dựa vào pre-commit thay vì kiểm chứng trong CI?
Không. Hook chỉ xử lý các tệp đã stage, cho phản hồi trong vòng một phút nhưng không bao phủ toàn bộ kho mã hay những thay đổi bỏ qua commit. CI chịu trách nhiệm về tính đầy đủ: giữ pre-commit trên các tệp đã stage và để CI lo việc quét toàn bộ.