3 điểm bởi GN⁺ 2023-11-30 | 1 bình luận | Chia sẻ qua WhatsApp
  • jaq là một bản sao của công cụ xử lý dữ liệu JSON jq, hướng tới một triển khai dễ dự đoán hơn trong khi vẫn duy trì khả năng tương thích với jq trong hầu hết trường hợp
  • Chương trình dòng lệnh jaq có thể dùng như một thay thế thả-vào-là-chạy cho jq, còn thư viện Rust jaq-core có thể biên dịch và chạy các chương trình jq bên trong chương trình Rust
  • Cung cấp hỗ trợ YAML, CBOR, TOML, XML mà jq không có; jaq-core có thể dùng an toàn trong môi trường đa luồng và hỗ trợ kiểu dữ liệu tùy ý vượt ra ngoài JSON
  • Trong đánh giá hiệu năng, jaq-3.0 nhanh nhất ở 20 trong 31 benchmark, jq-1.8.1 nhanh nhất ở 5 benchmark, còn gojq-0.12.18 nhanh nhất ở 6 benchmark
  • Về bảo mật, jaq cố gắng bảo đảm chống panic, an toàn bộ nhớ, và giới hạn I/O của dữ liệu đầu vào cũng như bộ lọc jq, nhưng không xử lý cạn kiệt tài nguyên như thời gian, bộ nhớ hay stack

jaq cung cấp những gì

  • jaq là một bản sao của công cụ xử lý dữ liệu JSON jq, phát âm là /ʒaːk/, giống Jacques
  • Có hỗ trợ các định dạng dữ liệu mà jq không có
    • YAML

    • CBOR

    • TOML

      • XML
      • manual riêng và có thể dùng thử trên playground
      • jaq được cung cấp dưới hai dạng
      • Chương trình dòng lệnh jaq: có thể dùng như một thay thế thả-vào-là-chạy cho jq
      • Thư viện jaq-core: có thể biên dịch và chạy các chương trình jq bên trong chương trình Rust

Mục tiêu thiết kế

  • Độ chính xác

    • jaq hướng tới một triển khai jq chính xác và dễ dự đoán hơn, trong khi vẫn duy trì khả năng tương thích với jq trong hầu hết trường hợp
  • Hiệu năng

    • jaq ban đầu được tạo ra vì thời gian khởi động dài của jq 1.6 gây bất tiện; trong môi trường đó, thời gian khởi động khoảng 50ms
    • Thời gian khởi động này đặc biệt nổi bật khi xử lý nhiều tệp nhỏ
    • Trong jq 1.7, thời gian khởi động đã được cải thiện đáng kể, nhưng jaq vẫn nhanh hơn jq trong nhiều benchmark
  • Sự đơn giản

    • jaq hướng tới một triển khai đơn giản và nhỏ gọn để giảm khả năng phát sinh lỗi và giúp việc đóng góp dễ dàng hơn

Cài đặt và build

  • Có thể tải binary cho Linux, Mac và Windows từ trang releases
  • Trên macOS hoặc Linux, có thể cài đặt bằng homebrew
    • brew install jaq
    • brew install --HEAD jaq
  • Để build từ mã nguồn, cần có Rust toolchain
  • Nếu đã clone repository, có thể build hoặc cài đặt bằng cargo build --release hoặc cargo install --locked --path jaq
  • jaq được kỳ vọng hoạt động trên mọi hệ thống mà Rust hỗ trợ; nếu không, dự án hướng dẫn hãy mở issue

Đánh giá hiệu năng

  • Đánh giá hiệu năng gồm nhiều benchmark so sánh jaq, jq và gojq
  • Benchmark empty đo thời gian khởi động bằng cách chạy bộ lọc empty n lần với đầu vào null
  • Benchmark bf-fib chạy một script Brainfuck tạo số Fibonacci bằng trình thông dịch Brainfuck viết bằng jq
  • Dữ liệu benchmark được tạo trên hệ thống Linux dùng AMD Ryzen 5 5500U
    • Lệnh được dùng là bench.sh target/release/jaq jq-1.8.1 gojq-0.12.17 | tee bench.json
    • Bảng hiển thị kết quả của jaq-3.0, jq-1.8.1, gojq-0.12.18 theo mili giây
    • N/A nghĩa là lỗi hoặc vượt quá 10 giây
  • Tóm tắt kết quả
    • jaq-3.0 nhanh nhất ở 20 benchmark
    • jq-1.8.1 nhanh nhất ở 5 benchmark
    • gojq-0.12.18 nhanh nhất ở 6 benchmark
  • gojq nhanh hơn nhiều ở tree-flatten vì triển khai bộ lọc flatten theo kiểu native thay vì dưới dạng định nghĩa

Mô hình bảo mật và giới hạn

  • jaq cố gắng bảo đảm những điều sau
    • Không xảy ra panic, ngoại trừ các trường hợp cạn kiệt tài nguyên
    • An toàn bộ nhớ, không làm hỏng bộ nhớ
    • Ngoại trừ việc đọc tệp trước khi chạy bộ lọc jq, dữ liệu đầu vào và bộ lọc jq không thể khởi động thao tác I/O
  • Trường hợp các bảo đảm này bị phá vỡ được xem là lỗi và nên được báo cáo
  • jaq không xử lý bất kỳ loại cạn kiệt tài nguyên nào
    • Thời gian chạy có thể kéo dài không giới hạn
    • Có thể dùng bộ nhớ không giới hạn
    • Có thể dùng không gian stack không giới hạn
  • Ví dụ, tràn stack có thể xảy ra khi đọc dữ liệu đầu vào hoặc chạy bộ lọc jq
    • jaq -nr 'repeat("[")' | jaq
    • jaq -n 'def f: 1+f; f'

Audit và kiểm thử

  • jaq core đã được Radically Open Security audit như một phần của hai khoản tài trợ NLnet
  • Trong các đợt audit bảo mật thứ nhấtthứ hai, các vấn đề có mức độ nghiêm trọng trung bình hoặc thấp đã được phát hiện
  • Tất cả vấn đề trong audit bảo mật đã được xử lý, và nhiều mục tiêu fuzzing cho jaq đã được thêm vào jaq-core/fuzz
  • Parser JSON của jaq, hifijson, cũng đã có các mục tiêu fuzzing từ trước
  • jaq có bộ test gồm hơn 500 bài kiểm thử

Trường hợp người dùng

  • Một người dùng đánh giá rằng jaq đã giúp ích rất nhiều so với việc tự triển khai hỗ trợ jq, và nhờ khả năng mở rộng thông qua trait ValT, họ có thể dễ dàng thêm hỗ trợ jq cho kiểu riêng của mình
  • Một người dùng khác cho biết chương trình Rust dùng jaq có thể chạy ba lần toàn bộ truy vấn trên toàn bộ tệp, trong khi Python jq PyPI crate và vòng lặp Python mới chạy một truy vấn trên toàn bộ tệp
  • Trong trường hợp trình thông dịch wsjq, có đánh giá rằng jaq nhanh hơn đáng kể so với các triển khai jq khác và điểm nhấn về độ chính xác rất ấn tượng
    • Trong benchmark wsjq đó, jaq nhanh hơn jq 5–10 lần và nhanh hơn gojq 15–196 lần
  • Một người dùng xử lý dữ liệu certificate transparency log bằng certstream-server gặp vấn đề với xử lý pipeline jq; sau khi chuyển sang jaq, nhờ thời gian khởi động nhanh hơn, họ có thể theo kịp ngay cả trên VM cấu hình thấp

Tài trợ

1 bình luận

 
GN⁺ 2023-11-30
Ý kiến trên Hacker News
  • Xét việc quá trình phát triển jq đã dừng trong 5 năm rồi gần đây mới sống lại, thì không lạ khi trong thời gian đó các báo cáo, dù là lỗi đã biết hay lỗi mới, đã tích tụ lại
    Giờ có lẽ dự án sẽ lấy lại tốc độ và từ từ xử lý danh sách tồn đọng đã chất đống lâu nay

  • Thích cách README giới thiệu kèm những dự án tương tự hoặc được truyền cảm hứng, chứ không phải là các dự án thay thế
    Nhờ README của dự án này mà biết đến https://github.com/yamafaktory/jql, đúng là công cụ đã tìm lâu nay nên rất cảm ơn
    Không có ý hạ thấp JAQ, nhưng cú pháp kiểu JQ quá khó hiểu nên jql hợp với tôi hơn

    • Nhìn từ góc độ này thì gron cũng hay
      Nó làm phẳng JSON thành các dòng dạng khóa-giá trị, giúp phù hợp với các thao tác stream đơn giản như grep: https://github.com/tomnomnom/gron
    • Phát hiện thú vị, tôi định sẽ thử dùng
      Tuy nhiên tôi đã kỳ vọng một trải nghiệm thật sự giống SQL. Không hiểu vì sao không đơn giản sao chép SQL để có thể truy vấn kiểu "SELECT * FROM $json WHERE x>1"
      Có vẻ ai cũng muốn tạo ra ngôn ngữ truy vấn ký hiệu khó hiểu của riêng mình như đang chơi code golf. Mong là thoát khỏi kiểu cú pháp Unix cũ cực ngắn nhưng không hiển nhiên, và tiến gần hơn tới cách làm của PowerShell
    • https://github.com/tidwall/jj cũng đáng xem thử
    • Tôi phần nào đồng cảm với sự bất tiện đó, nhưng ít nhất jql trông không giống lời giải
      |={"b""d"=2, "c"} có vẻ mang nghĩa giống select(."b"."d" == 2 or ."c" != null) của jq, nhưng dù jq dài hơn, tôi thấy nó rõ ràng hơn
      Thực tế có lẽ cần .[] | select(...), nhưng jql cũng có thể có giả định tương tự; tôi không chắc ví dụ có đầy đủ không nên điều đó không ảnh hưởng nhiều đến kết luận
    • Tính đồng hình mã-dữ liệu (homoiconicity) của jql trông khá giống Lisp
      Có vẻ cũng có thể áp dụng lên chính nó hoặc dùng “macro”
  • Tôi thích ý tưởng của jq, nhưng vì không dùng thường xuyên nên mỗi lần muốn làm gì đó lại phải tra cú pháp trong tài liệu
    Đáng tiếc là 99% việc tôi làm với jq là | jq .

    • Tôi cũng gặp vấn đề tương tự
      Riêng chuyện khác, tôi bắt đầu tạo một ngôn ngữ cấu hình, rồi hóa ra nó cũng khá ổn cho truy vấn JSON: https://docs.ruuda.nl/rcl/rcl_query/
      Ở đây có một ví dụ mà tôi không giải được bằng jq nhưng giải được bằng RCL: https://fosstodon.org/@ruuda/111120049523534027
    • Vì cùng vấn đề đó nên tôi chưa tận dụng được sức mạnh của jq, nhưng trong những trường hợp như vậy, có Copilot thực sự rất hữu ích
      Chỉ cần đưa yêu cầu và một mẫu JSON nguồn đã rút gọn, nó sẽ tạo đúng script jq cần thiết
      Với yêu cầu phức tạp, thay vì cố mô tả chính xác ngay từ đầu, việc lặp dần với Copilot để dẫn nó tới lời giải sẽ dễ và ổn định hơn. Trong quá trình lặp, đôi khi còn nảy ra ý tưởng tốt hơn ban đầu
      ChatGPT hay các công cụ khác có lẽ cũng hoạt động tương tự
    • Gần đây tôi đã dùng ChatGPT để nhanh chóng lấy cú pháp jq cần thiết: https://chat.openai.com/share/40b68d73-d2dd-412d-867f-9f375e...
  • https://github.com/01mf02/jaq/blob/main/Cargo.lock
    Có khá nhiều phụ thuộc

    • So với gojq thì thật sự nhiều: https://github.com/itchyny/gojq/blob/main/go.mod
    • Tò mò trong hệ sinh thái Rust thì những trường hợp như thế này thường diễn biến ra sao
      Khi có nhiều phụ thuộc, theo thời gian có vẻ rủi ro chúng trở nên không tương thích căn bản với nhau sẽ tăng lên, và việc bảo trì có lẽ sẽ thành chuyện lớn
      Ví dụ không biết 2 năm nữa có còn biên dịch tốt không
  • jq là một công cụ rất mạnh, nhưng gần đây DuckDB cũng được dùng nhiều
    Nếu dữ liệu ở mức nào đó có dạng bảng, SQL là ngôn ngữ tự nhiên hơn nhiều

    • Trước đây tôi từng dùng Retool và thấy có “Query JSON with SQL”, khá tiện: https://docs.retool.com/queries/guides/sql/query-json
      Nó hơi giống LINQ của C#, nhưng SQL được chuẩn hóa hơn nên tôi thích hơn
      Nếu có thể truy vấn các collection thô bằng SQL ngay trong ngôn ngữ thì sẽ rất tuyệt, và xa hơn nữa, nếu có thể lưu collection một cách trong suốt vào Sqlite thì còn tốt hơn
      Mỗi khi thấy mã lấy dữ liệu từ cơ sở dữ liệu rồi xử lý đơn giản bằng vòng lặp hoặc stream API, tôi luôn thấy tiếc. Với các mục đích như vậy, SQL ở mức trừu tượng cao hơn và súc tích hơn nhiều so với Java/Kotlin/Python/JavaScript
    • Tôi cũng có cảm giác tương tự
      Tôi lưu toàn bộ đầu ra JSON gốc vào bảng sqlite, rồi tạo cột ảo ở đó và đưa kết quả select chạy qua vòng lặp shell
      Các vòng lặp lồng nhau được tháo gỡ, và khả năng debug tốt hơn nhiều vì có thể kiểm tra đúng bản ghi trong DB rồi chạy lại
      Tôi nhận ra thứ mình đang tạo là một DAG, và luôn bắt đầu lại từ bản ghi được xử lý thành công gần nhất. Tôi tò mò liệu có công cụ nào giống Make để biểu diễn việc này không
      Make không có target SQL, còn các bộ xử lý DAG đầy đủ như Airflow thì quá nặng để ghép các đoạn shell
    • Đúng vậy. Với dữ liệu quan hệ có schema nghiêm ngặt, SQL tốt hơn nhiều
      Dù vậy, cách biểu diễn truy vấn đệ quy một cách ngắn gọn trong SQL vẫn là thứ khó có được
    • Cá nhân tôi thấy textql phù hợp hơn cho việc này. Mô hình trong đầu đơn giản hơn
      https://github.com/dinedal/textql
  • Xét về độ chính xác, tôi tò mò liệu nó có thể hiển thị số uint64 mà không bị cắt hay không
    Đây hiện là điểm khó chịu nhất của tôi với jq

    • Đáng tiếc là nếu xem số JSON như số thực dấu phẩy động 64-bit, thì theo chuẩn phải xử lý như vậy, và độ chính xác số nguyên sẽ là 53 bit
      Tuy nhiên, đặc tả mới nhất là RFC 8259 đã đính chính rằng nó chỉ quy định dạng văn bản của số, chứ không quy định ngữ nghĩa
      Trên thực tế, phần lớn implementation coi JSON như một tập con của JavaScript, dẫn đến giả định rằng số là số thực dấu phẩy động 64-bit
    • Tôi nhớ là jq 1.7 đã cải thiện điều này: https://github.com/jqlang/jq/releases/tag/jq-1.7
      Có nói rằng nó dùng literal số thập phân để giữ độ chính xác, và các phép so sánh tôn trọng độ chính xác, nhưng phép toán số học vẫn có thể bị cắt
    • jq 1.7 giữ được số nguyên lớn, nhưng nếu thực hiện bất kỳ phép toán nào trên đó thì sẽ bị cắt
      Hiện tại nó cắt về decimal64, hơi gây nhầm lẫn, nhưng bản phát hành tiếp theo dự kiến sẽ sửa để cắt về binary64(double) theo khuyến nghị của đặc tả JSON: https://github.com/jqlang/jq/pull/2949
  • Từ khi chuyển sang jless thì tôi không nhìn lại nữa
    Giao diện người dùng vượt xa những công cụ khác

    • Không cùng loại
      jq không chỉ là một trình xem đơn thuần, mà là bộ xử lý ngôn ngữ truy vấn JSON
  • Việc đâu đó trong Rust có thư viện vẽ line art trên terminal thì khá dễ thương, nhưng khi chạy jaq, nó xả hàng megabyte escape code vào iTerm, cuối cùng iTerm còn cố in ra máy in
    Nó đã tỏ ra quá thông minh
    Trong ngữ cảnh như echo *json | rush -- jaq -rf ./this-program.jq {} | datamash ..., tôi không nghĩ việc cố đưa hiệu ứng nghệ thuật vào TTY là phù hợp
    Dù sao thì nguyên nhân lỗi là jaq không có strftime

  • Ấn tượng đầu tiên là có thông báo lỗi đẹp, nhưng không có halt_error/0
    Sau khi comment halt_error đi, nó chậm hơn jq và gojq
    Với cùng đầu vào, jq mất khoảng 0,023 giây, gojq khoảng 0,070 giây, còn jaq khoảng 0,103 giây
    File aoc22-13.jq được dùng là https://pastebin.com/raw/YiUjEu2n, còn input.txthttps://pastebin.com/raw/X0FSyTNf

  • Tôi bắt đầu dùng yq thay cho jq, và tò mò liệu có khác biệt quan trọng nào không

    • Còn tùy là yq nào
      Cá nhân tôi thích https://github.com/mikefarah/yq hơn https://github.com/kislyuk/yq
    • jq có cảm giác là một công cụ vững chắc hơn yq rất nhiều
      Tôi hiểu rằng xử lý YAML khó hơn JSON rất nhiều, nhưng dù yq đã đổi cú pháp từ phiên bản 3 sang 4 để gần với jq hơn, bằng cách nào đó nó vẫn không hoàn toàn giống
      Ngoài ra yq không có if-then-else, nên trông như thiết kế chưa tốt hoặc bị bỏ sót: https://github.com/mikefarah/yq/issues/95
      Khi cần xử lý YAML thì yq hoạt động tốt và cũng xử lý comment khá ổn, nhưng với xử lý JSON thuần túy thì jq là công cụ tốt hơn