JSONPath, jq hay bộ lọc JavaScript

Cùng năm câu hỏi được trả lời bằng JSONPath (RFC 9535), jq và JavaScript thuần, kèm kết quả, ưu nhược điểm và những lỗi mỗi cách dễ dẫn tới.

Trong bài này

Khi cần lấy vài giá trị ra khỏi một tài liệu JSON, có ba công cụ thông dụng: JSONPath, ngôn ngữ truy vấn nay đã được chuẩn hóa thành RFC 9535; jq, trình xử lý dòng lệnh có ngôn ngữ lập trình nhỏ của riêng nó; và mã thông thường, như filter và map của JavaScript. Chúng chồng lấn nhưng không thay thế được cho nhau. Bài này trả lời cùng các câu hỏi bằng cả ba cách trên tài liệu cửa hàng sách trong RFC 9535, chỉ ra mỗi cách hợp với việc gì, và liệt kê những lỗi mỗi cách khiến ta dễ mắc. Các câu trả lời JSONPath chạy được ngay trong công cụ kiểm tra JSONPath, vốn nạp sẵn tài liệu đó làm mẫu.

Tài liệu

Một cửa hàng có bốn cuốn sách (mỗi cuốn có thể loại, tác giả, tên và giá, hai cuốn có thêm isbn) và một chiếc xe đạp đỏ giá 399. Giá sách lần lượt là 8.95, 12.99, 8.99 và 22.99.

Câu hỏi 1: mọi tác giả

  • JSONPath: $.store.book[*].author
  • jq: .store.book[].author
  • JavaScript: data.store.book.map(b => b.author)

Cả ba đều trả về Nigel Rees, Evelyn Waugh, Herman Melville và J. R. R. Tolkien. JSONPath trả về danh sách nút, mỗi nút kèm vị trí; jq phát ra bốn giá trị riêng lẻ (bọc biểu thức trong [...] để có mảng); JavaScript cho bạn ngay một mảng.

Câu hỏi 2: tên sách giá dưới 10

  • JSONPath: $..book[[email protected] < 10].title
  • jq: .store.book[] | select(.price < 10) | .title
  • JavaScript: data.store.book.filter(b => b.price < 10).map(b => b.title)

Kết quả: "Sayings of the Century" và "Moby Dick". JSONPath còn cho biết chúng nằm ở đâu: $['store']['book'][0]['title'] và $['store']['book'][2]['title'], tức "đường dẫn chuẩn hóa" mà RFC 9535 định nghĩa. Điều đó hữu ích khi bước tiếp theo là sửa giá trị tại chỗ hoặc báo bản ghi nào không qua kiểm tra.

Câu hỏi 3: mọi giá, ở mọi độ sâu

  • JSONPath: $..price
  • jq: [.. | objects | select(has("price")) | .price]
  • JavaScript: một hàm đệ quy duyệt qua đối tượng và mảng.

Toán tử hậu duệ .. là điểm mạnh nhất của JSONPath: 8.95, 12.99, 8.99, 22.99 và 399, theo đúng thứ tự trong tài liệu. Với JavaScript, bạn phải tự viết và kiểm thử phần đệ quy, và rất dễ quên mảng hoặc lặp vô hạn khi có tham chiếu dùng chung.

Câu hỏi 4: sách có ISBN

  • JSONPath: $..book[[email protected]]
  • jq: .store.book[] | select(has("isbn"))
  • JavaScript: data.store.book.filter(b => "isbn" in b)

Có một khác biệt tinh tế: [email protected] của JSONPath kiểm tra thành viên có tồn tại hay không, nên isbn bằng null hay "" vẫn được tính. Trong jq, select(.isbn) sẽ bỏ các giá trị null và false, vì thế has mới là bản dịch trung thành. Trong JavaScript, dùng b.isbn làm điều kiện cũng bỏ chuỗi rỗng và số không.

Câu hỏi 5: thứ JSONPath không làm được

"Tổng giá các sách thuộc thể loại fiction" cần phép cộng:

  • jq: [.store.book[] | select(.category == "fiction") | .price] | add cho 44.97.
  • JavaScript: .filter(...).reduce((s, b) => s + b.price, 0) cũng cho 44.97 (12.99 + 8.99 + 22.99).
  • JSONPath: không. Nó chỉ chọn; nó không bao giờ tính ra giá trị mới, dựng đối tượng hay cộng dồn.

Đó là ranh giới. JSONPath là ngôn ngữ chọn lọc, cố ý nhỏ gọn để có thể nhúng an toàn vào cấu hình (AWS Step Functions dùng đường dẫn JSONPath như $.detail để chọn đầu vào; kubectl -o jsonpath của Kubernetes dùng một phương ngữ gần giống). jq là ngôn ngữ lập trình để biến đổi dữ liệu trên dòng lệnh. JavaScript, hay bất kỳ ngôn ngữ đa dụng nào, dành cho khi logic vượt quá một biểu thức.

Chọn cách nào

Bạn cầnDùng
Trỏ tới giá trị trong tệp cấu hình hay bài kiểm thử APIJSONPath
Biết từng kết quả nằm ở đâuJSONPath (đường dẫn chuẩn hóa)
Biến đổi, sắp xếp lại hay cộng dồn JSON trong script shelljq
Logic nhiều bước, xử lý lỗi, có kiểm thửNgôn ngữ lập trình của bạn
Truy vấn JSON bên trong PostgreSQLSQL/JSON path (jsonb_path_query), một ngôn ngữ khác nhưng tương tự

Những lỗi hay gặp

  • Trộn phương ngữ. Nhiều công cụ đã cài đặt JSONPath trước chuẩn năm 2024, mỗi nơi một chút khác. Biểu thức script như [(@.length-1)] và bộ lọc viết [?(@.price < 10)] với quy tắc riêng đến từ các bản cài đặt cũ. RFC 9535 dùng [-1] và [[email protected] < 10] (vẫn được đặt điều kiện trong ngoặc tròn).
  • So sánh một truy vấn nhiều giá trị. [[email protected][*] == 'sale'] là lỗi trong RFC 9535, vì @.tags[*] có thể cho nhiều giá trị. Hãy lọc ở cấp sâu hơn một bậc, hoặc dùng count().
  • Trông đợi tự đổi kiểu. [[email protected] == '42'] không khớp với số 42.
  • Quên rằng jq phát luồng giá trị. .store.book[].title in ra nhiều giá trị JSON, không phải một mảng; công cụ cần đúng một tài liệu JSON thì phải bọc [...].
  • Dùng eval để chạy "JSONPath". Một số thư viện cũ chạy biểu thức lọc như JavaScript, tức là chạy bất kỳ mã nào nằm trong tài liệu hay truy vấn. Các bản cài đặt theo chuẩn, kể cả công cụ kiểm tra này, phân tích biểu thức thay vì thực thi.

Thử ngay

Nạp mẫu trong công cụ kiểm tra JSONPath, chạy các truy vấn ở trên và so đường dẫn với kết quả trong bài. Để định dạng hoặc kiểm tra JSON trước, dùng công cụ định dạng JSON.

Công cụ được nhắc trong bài

Về trang blog