3 คะแนน โดย GN⁺ 2023-11-30 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • jaq เป็นโคลนของ jq เครื่องมือประมวลผลข้อมูล JSON โดยตั้งเป้าให้เข้ากันได้กับ jq ในกรณีส่วนใหญ่ พร้อมมุ่งสู่การเป็นอิมพลีเมนเทชันที่แม่นยำและคาดเดาได้มากกว่า
  • โปรแกรมบรรทัดคำสั่ง jaq สามารถใช้เป็น ตัวแทนแบบดรอปอิน ของ jq ได้ และไลบรารี Rust jaq-core สามารถคอมไพล์และรันโปรแกรม jq ภายในโปรแกรม Rust ได้
  • รองรับ YAML, CBOR, TOML และ XML ซึ่ง jq ไม่มี และ jaq-core ก็สามารถใช้งานได้อย่างปลอดภัยในสภาพแวดล้อมมัลติเธรด พร้อมรองรับ ชนิดข้อมูลตามอำเภอใจ ที่นอกเหนือจาก JSON
  • ในการประเมินประสิทธิภาพ jaq-3.0 เร็วที่สุดใน 20 จาก 31 เบนช์มาร์ก, jq-1.8.1 เร็วที่สุดใน 5 รายการ, และ gojq-0.12.18 เร็วที่สุดใน 6 รายการ
  • ในด้านความปลอดภัย พยายามรับประกันการป้องกัน panic, ความปลอดภัยของหน่วยความจำ, และการจำกัด I/O ของข้อมูลนำเข้าและ jq filter แต่ไม่รองรับ การใช้ทรัพยากรจนหมด เช่น เวลา หน่วยความจำ หรือสแตก

สิ่งที่ jaq มีให้

  • jaq เป็นโคลนของ jq เครื่องมือประมวลผลข้อมูล JSON และออกเสียงว่า /ʒaːk/ เหมือน Jacques
  • รองรับรูปแบบข้อมูลที่ jq ไม่มี
    • YAML

    • CBOR

    • TOML

      • XML
      • มี manual แยกต่างหาก และสามารถลองใช้งานได้ใน playground
      • jaq มีให้ใช้งานใน 2 รูปแบบ
      • โปรแกรมบรรทัดคำสั่ง jaq: ใช้เป็น ตัวแทนแบบดรอปอิน ของ jq ได้
      • ไลบรารี jaq-core: สามารถคอมไพล์และรันโปรแกรม jq ภายในโปรแกรม Rust ได้

เป้าหมายการออกแบบ

  • ความแม่นยำ

    • jaq ตั้งเป้าเป็นอิมพลีเมนเทชัน jq ที่แม่นยำและคาดเดาได้มากขึ้น โดยยังคงความเข้ากันได้กับ jq ในกรณีส่วนใหญ่
  • ประสิทธิภาพ

    • เดิมที jaq ถูกสร้างขึ้นเพราะเวลาเริ่มทำงานที่ยาวนานของ jq 1.6 ทำให้ใช้งานไม่สะดวก โดยในสภาพแวดล้อมนั้นเวลาเริ่มทำงานอยู่ที่ประมาณ 50ms
    • เวลาเริ่มทำงานนี้จะยิ่งเห็นชัดเมื่อประมวลผลไฟล์ขนาดเล็กจำนวนมาก
    • แม้ jq 1.7 จะปรับปรุงเวลาเริ่มทำงานได้อย่างมาก แต่ jaq ก็ยังเร็วกว่า jq ในหลายเบนช์มาร์ก
  • ความเรียบง่าย

    • jaq มุ่งสู่อิมพลีเมนเทชันที่เล็กและเรียบง่าย เพื่อลดโอกาสเกิดบั๊กและทำให้มีส่วนร่วมพัฒนาได้ง่าย

การติดตั้งและการบิลด์

  • สามารถดาวน์โหลดไบนารีสำหรับ Linux, Mac และ Windows ได้จาก releases page
  • บน macOS หรือ Linux สามารถติดตั้งผ่าน homebrew ได้
    • brew install jaq
    • brew install --HEAD jaq
  • หากต้องการบิลด์จากซอร์ส จะต้องมี Rust toolchain
  • หากโคลนรีโพซิทอรีมาแล้ว สามารถบิลด์หรือติดตั้งได้ด้วย cargo build --release หรือ cargo install --locked --path jaq
  • jaq ควรทำงานได้บนทุกระบบที่ Rust รองรับ และหากไม่เป็นเช่นนั้น ก็ขอให้เปิด issue

การประเมินประสิทธิภาพ

  • การประเมินประสิทธิภาพประกอบด้วยหลายเบนช์มาร์กที่เปรียบเทียบ jaq, jq และ gojq
  • เบนช์มาร์ก empty วัด เวลาเริ่มทำงาน โดยรันฟิลเตอร์ empty จำนวน n ครั้งกับอินพุต null
  • เบนช์มาร์ก bf-fib รันสคริปต์ Brainfuck ที่สร้างเลข Fibonacci ด้วยอินเทอร์พรีเตอร์ Brainfuck ที่เขียนด้วย jq
  • ข้อมูลเบนช์มาร์กสร้างขึ้นบนระบบ Linux ที่ใช้ AMD Ryzen 5 5500U
    • คำสั่งที่ใช้คือ bench.sh target/release/jaq jq-1.8.1 gojq-0.12.17 | tee bench.json
    • ตารางจะแสดงผลของ jaq-3.0, jq-1.8.1, gojq-0.12.18 ในหน่วยมิลลิวินาที
    • N/A หมายถึงเกิดข้อผิดพลาดหรือใช้เวลานานเกิน 10 วินาที
  • สรุปผล
    • jaq-3.0 เร็วที่สุดใน 20 เบนช์มาร์ก
    • jq-1.8.1 เร็วที่สุดใน 5 เบนช์มาร์ก
    • gojq-0.12.18 เร็วที่สุดใน 6 เบนช์มาร์ก
  • gojq เร็วกว่ามากใน tree-flatten เพราะอิมพลีเมนต์ฟิลเตอร์ flatten แบบเนทีฟ แทนที่จะนิยามเป็นฟิลเตอร์

โมเดลความปลอดภัยและข้อจำกัด

  • jaq พยายามรับประกันสิ่งต่อไปนี้
    • ไม่เกิด panic ยกเว้นในกรณีทรัพยากรถูกใช้จนหมด
    • ความปลอดภัยของหน่วยความจำ โดยไม่ทำให้หน่วยความจำเสียหาย
    • ยกเว้นการอ่านไฟล์ก่อนรัน jq filter แล้ว ข้อมูลนำเข้าและ jq filter จะไม่สามารถเริ่มงาน I/O ได้
  • หากการรับประกันเหล่านี้ถูกทำลาย จะถือเป็นบั๊กและควรรายงาน
  • jaq ไม่ได้รองรับ การใช้ทรัพยากรจนหมด ไม่ว่าประเภทใดก็ตาม
    • เวลาในการรันอาจยาวนานได้ไม่จำกัด
    • อาจใช้หน่วยความจำได้ไม่จำกัด
    • อาจใช้พื้นที่สแตกได้ไม่จำกัด
  • ตัวอย่างเช่น อาจเกิด stack overflow ได้ระหว่างอ่านข้อมูลนำเข้าหรือรัน jq filter
    • jaq -nr 'repeat("[")' | jaq
    • jaq -n 'def f: 1+f; f'

การตรวจสอบและการทดสอบ

  • jaq core ได้รับการตรวจสอบโดย Radically Open Security ภายใต้ส่วนหนึ่งของเงินสนับสนุน NLnet สองครั้ง
  • การตรวจสอบความปลอดภัย ครั้งแรก และครั้งที่สอง พบประเด็นที่มีความรุนแรงระดับกลางหรือต่ำ
  • ประเด็นทั้งหมดจากการตรวจสอบความปลอดภัยได้รับการจัดการแล้ว และมีการเพิ่มเป้าหมายสำหรับการ fuzz หลายรายการให้กับ jaq-core/fuzz
  • ตัวแยกวิเคราะห์ JSON ของ jaq อย่าง hifijson ก็มีเป้าหมาย fuzz อยู่แล้วเช่นกัน
  • jaq มีชุดทดสอบที่ประกอบด้วย การทดสอบมากกว่า 500 รายการ

กรณีการใช้งานของผู้ใช้

  • ผู้ใช้รายหนึ่งประเมินว่า jaq ช่วยได้มากกว่าการพยายามอิมพลีเมนต์การรองรับ jq เองโดยตรง และด้วยความสามารถในการขยายผ่าน trait ValT ทำให้เพิ่มการรองรับ jq ให้กับชนิดข้อมูลของตนเองได้ง่าย
  • ผู้ใช้อีกรายระบุว่า โปรแกรม Rust ที่ใช้ jaq สามารถรันทุกคิวรีกับทั้งไฟล์ได้ สามรอบ ในช่วงเวลาที่ Python jq PyPI crate พร้อมลูป Python รันได้เพียงหนึ่งคิวรีกับทั้งไฟล์
  • ในกรณีของอินเทอร์พรีเตอร์ wsjq มีการประเมินว่า jaq เร็วกว่าอิมพลีเมนเทชัน jq อื่นอย่างมาก และความใส่ใจด้านความแม่นยำก็น่าประทับใจ
    • ในเบนช์มาร์ก wsjq นั้น jaq เร็วกว่า jq 5–10 เท่า และเร็วกว่า gojq 15–196 เท่า
  • ผู้ใช้ที่ประมวลผลข้อมูล certificate transparency log ด้วย certstream-server พบปัญหากับการ pipe ผ่าน jq และหลังเปลี่ยนมาใช้ jaq ก็สามารถตามงานได้ทันแม้บน VM สเปกต่ำ ด้วยเวลาเริ่มทำงานที่เร็วกว่า

เงินสนับสนุน

1 ความคิดเห็น

 
GN⁺ 2023-11-30
ความคิดเห็นจาก Hacker News
  • เมื่อคำนึงว่า การพัฒนา jq หยุดไปนาน 5 ปี แล้วเพิ่งกลับมาฟื้นตัวเมื่อไม่นานมานี้ ก็ไม่น่าแปลกที่รายงานต่าง ๆ จะสะสมค้างอยู่ในช่วงนั้น ไม่ว่าจะเป็นบั๊กที่รู้จักอยู่แล้วหรือบั๊กใหม่
    ตอนนี้น่าจะเริ่มกลับมาเดินหน้าได้เร็วขึ้น และค่อย ๆ จัดการ รายการค้างที่ยังไม่ปิด ซึ่งสะสมมานาน

  • ชอบแนวทางที่ README แนะนำโปรเจกต์ที่คล้ายกันหรือได้แรงบันดาลใจมา ซึ่งไม่ใช่ตัวทดแทนโดยตรงไว้ด้วยกัน
    ได้รู้จัก https://github.com/yamafaktory/jql จาก README ของโปรเจกต์นี้ และรู้สึกขอบคุณ เพราะเป็นเครื่องมือที่ตามหามานาน
    ไม่ได้ตั้งใจจะลดคุณค่าของ JAQ แต่ ไวยากรณ์แบบ JQ เข้าใจยากเกินไป สำหรับผม jql เหมาะกว่า

    • จากมุมมองนี้ gron ก็ดีเหมือนกัน
      มันทำให้ JSON แบนลงเป็นบรรทัดรูปแบบคีย์-ค่า จึงเข้ากับงานสตรีมแบบเรียบง่ายอย่าง grep ได้ดี: https://github.com/tomnomnom/gron
    • เป็นการค้นพบที่น่าสนใจ เลยคิดว่าจะลองใช้ดู
      แต่จริง ๆ แล้วคาดหวัง ประสบการณ์แบบ SQL มากกว่า ไม่เข้าใจว่าทำไมไม่ลอก SQL มาเลย แล้วให้ query ได้แบบ "SELECT * FROM $json WHERE x>1"
      ดูเหมือนทุกคนอยากสร้างภาษา query เชิงสัญลักษณ์ลึกลับของตัวเอง ราวกับกำลังแข่ง code golf กันอยู่ อยากให้หลุดจากไวยากรณ์แบบ Unix เก่า ๆ ที่สั้นสุดขีดแต่ไม่ชัดเจน แล้วขยับไปใกล้แนวทางของ PowerShell มากขึ้น
    • https://github.com/tidwall/jj ก็น่าลองดู
    • พอเข้าใจความไม่สะดวกแบบนั้นอยู่บ้าง แต่สำหรับผมอย่างน้อย jql ก็ดูไม่เหมือนคำตอบ
      |={"b""d"=2, "c"} ดูเหมือนจะมีความหมายคล้าย select(."b"."d" == 2 or ."c" != null) ของ jq ซึ่งฝั่ง jq ถึงจะยาวกว่า แต่รู้สึกชัดเจนกว่า
      ในทางปฏิบัติคงต้องใช้ .[] | select(...) ด้วย แต่ jql ก็อาจมีสมมติฐานคล้ายกัน และไม่แน่ใจว่าตัวอย่างครบถ้วนไหม จึงไม่กระทบข้อสรุปมากนัก
    • homoiconicity ของ jql ดูค่อนข้างคล้าย Lisp
      ดูเหมือนอาจนำไปใช้กับตัวมันเอง หรือใช้ “แมโคร” ได้ด้วย
  • ชอบไอเดียของ jq แต่เพราะไม่ได้ใช้บ่อย ทุกครั้งที่อยากทำอะไรสักอย่างก็ต้อง เปิดคู่มือหาไวยากรณ์
    น่าเสียดายที่ 99% ของงานที่ทำด้วย jq คือ | jq .

    • เคยเจอปัญหาเดียวกัน
      แยกไปเริ่มทำภาษา config ขึ้นมาเอง แล้วพบว่ามันใช้ query JSON ได้ค่อนข้างดีด้วย: https://docs.ruuda.nl/rcl/rcl_query/
      มีตัวอย่างที่แก้ด้วย jq ไม่ได้ แต่แก้ด้วย RCL ได้อยู่ที่นี่: https://fosstodon.org/@ruuda/111120049523534027
    • ด้วยปัญหาเดียวกัน ทำให้ใช้พลังของ jq ได้ไม่เต็มที่ แต่กรณีแบบนี้การมี Copilot ช่วยได้จริง ๆ
      ถ้าให้ทั้งงานที่ต้องการทำและตัวอย่าง JSON ต้นฉบับแบบย่อ มันจะสร้างสคริปต์ jq ที่ถูกต้องให้
      สำหรับข้อกำหนดที่ซับซ้อน การค่อย ๆ วนซ้ำกับ Copilot เพื่อพาไปสู่คำตอบมักง่ายและเสถียรกว่าการพยายามอธิบายให้ถูกต้องครบถ้วนในครั้งเดียว ระหว่างวนซ้ำ บางทียังนึกไอเดียที่ดีกว่าตอนแรกได้ด้วย
      ChatGPT หรือเครื่องมืออื่น ๆ ก็น่าจะทำงานคล้ายกัน
    • ช่วงหลังใช้ ChatGPT เพื่อหา ไวยากรณ์ jq ที่ต้องการได้อย่างรวดเร็ว: https://chat.openai.com/share/40b68d73-d2dd-412d-867f-9f375e...
  • https://github.com/01mf02/jaq/blob/main/Cargo.lock
    มี dependency ค่อนข้างมาก

    • ถ้าเทียบกับ gojq ถือว่าเยอะจริง ๆ: https://github.com/itchyny/gojq/blob/main/go.mod
    • สงสัยว่าใน ecosystem ของ Rust กรณีแบบนี้มักจะเป็นไปอย่างไร
      ถ้ามี dependency มาก ความเสี่ยงที่เวลาผ่านไปแล้วแต่ละตัวจะเข้ากันไม่ได้ในระดับพื้นฐานก็ดูสูงขึ้น และน่าจะกลายเป็นงานบำรุงรักษาใหญ่
      เช่น อีก 2 ปีให้หลังยังจะคอมไพล์ ได้ดีอยู่ไหม
  • jq เป็นเครื่องมือที่ทรงพลังมาก แต่ช่วงนี้ DuckDB ก็ถูกใช้กันเยอะเช่นกัน
    ถ้าข้อมูลมีลักษณะเป็นตารางอยู่บ้าง SQL จะเป็นภาษาที่เป็นธรรมชาติกว่ามาก

    • ก่อนหน้านี้เคยลองใช้ Retool แล้วมี “Query JSON with SQL” ซึ่งสะดวกทีเดียว: https://docs.retool.com/queries/guides/sql/query-json
      คล้ายกับ LINQ ของ C# อยู่บ้าง แต่ชอบ SQL มากกว่าเพราะเป็นมาตรฐานมากกว่า
      ถ้าสามารถ query คอลเลกชันดิบในภาษาได้ด้วย SQL ก็คงยอดเยี่ยม และยิ่งกว่านั้นถ้าสามารถเก็บคอลเลกชันลง Sqlite ได้แบบโปร่งใสก็จะยิ่งดี
      เวลาเห็นโค้ดที่ดึงข้อมูลจากฐานข้อมูลหรือแหล่งอื่น ๆ แล้วมาประมวลผลง่าย ๆ ด้วยลูปหรือ stream API มักรู้สึกเสียดายเสมอ สำหรับการใช้งานแบบนี้ SQL เป็นระดับสูงกว่าและกระชับกว่า Java/Kotlin/Python/JavaScript มาก
    • รู้สึกคล้ายกัน
      เก็บเอาต์พุต JSON ต้นฉบับทั้งหมดลงใน ตาราง sqlite จากนั้นสร้างคอลัมน์เสมือน แล้วเอาผลลัพธ์ select ไปวนในเชลล์ลูป
      ลูปซ้อนถูกคลี่ออก และสามารถตรวจสอบเรคคอร์ดที่ถูกต้องใน DB แล้วรันซ้ำได้ ทำให้ดีบักได้ง่ายขึ้นมาก
      เริ่มตระหนักว่าสิ่งที่กำลังทำอยู่คือ DAG และมักเริ่มใหม่จากเรคคอร์ดล่าสุดที่ประมวลผลสำเร็จเสมอ เลยสงสัยว่ามีเครื่องมือคล้าย Make ที่ใช้แทนสิ่งนี้ได้ไหม
      Make ไม่มี target แบบ SQL และตัวประมวลผล DAG เต็มรูปแบบอย่าง Airflow ก็หนักเกินไปสำหรับการประกอบชิ้นส่วนเชลล์เล็ก ๆ
    • ใช่แล้ว สำหรับ ข้อมูลเชิงสัมพันธ์ ที่มีสคีมาเข้มงวด SQL ดีกว่ามาก
      ถึงอย่างนั้น วิธีเขียน recursive query ใน SQL ให้กระชับก็ยังหาได้ยากอยู่ดี
    • สำหรับงานแบบนี้ ส่วนตัวชอบ textql มากกว่า โมเดลในหัวเรียบง่ายกว่า
      https://github.com/dinedal/textql
  • ในแง่ความถูกต้อง อยากรู้ว่าสามารถแสดง ตัวเลข uint64 โดยไม่ถูกตัดได้หรือไม่
    นี่เป็นจุดที่รบกวนใจที่สุดใน jq ตอนนี้

    • น่าเสียดายที่ถ้ามองตัวเลข JSON เป็น floating point 64 บิต หากทำตามมาตรฐานก็ต้องจัดการแบบนั้น และความแม่นยำของจำนวนเต็มจะเหลือ 53 บิต
      อย่างไรก็ตาม สเปกล่าสุดอย่าง RFC 8259 ได้แก้ความเข้าใจว่า ระบุเพียงรูปแบบข้อความของตัวเลขเท่านั้น ไม่ได้กำหนด semantics
      ในทางปฏิบัติ implementation ส่วนใหญ่ถือว่า JSON เป็น subset ของ JavaScript จึงนำไปสู่สมมติฐานว่าตัวเลขเป็น floating point 64 บิต
    • เข้าใจว่าปรับปรุงแล้วใน jq 1.7: https://github.com/jqlang/jq/releases/tag/jq-1.7
      ระบุว่าใช้ decimal number literal เพื่อรักษาความแม่นยำ และ comparison operator เคารพความแม่นยำ แต่ arithmetic operator อาจถูกตัดได้
    • jq 1.7 รักษาจำนวนเต็มขนาดใหญ่ไว้ได้ แต่ถ้ามี operation ใด ๆ บนค่านั้นก็จะถูกตัด
      ตอนนี้ตัดเป็น decimal64 เลยชวนสับสนเล็กน้อย แต่ใน release ถัดไปจะปรับให้ตัดเป็น binary64(double) ตามข้อเสนอของสเปก JSON: https://github.com/jqlang/jq/pull/2949
  • ตั้งแต่ย้ายไปใช้ jless ก็ไม่เคยมองย้อนกลับมา
    user interface ล้ำหน้ากว่าตัวอื่นมาก

    • ไม่ใช่เครื่องมือประเภทเดียวกัน
      jq ไม่ใช่แค่ viewer ธรรมดา แต่เป็น ตัวประมวลผลภาษา query สำหรับ JSON
  • น่ารักดีที่ Rust มีไลบรารี terminal line art อยู่ที่ไหนสักแห่ง แต่พอลองรัน jaq แล้วมันเท escape code เป็นเมกะไบต์ใส่ iTerm จนสุดท้าย iTerm พยายามจะพิมพ์ออกเครื่องพิมพ์
    มันฉลาดเกินไปหน่อย
    ในบริบทอย่าง echo *json | rush -- jaq -rf ./this-program.jq {} | datamash ... ผมว่าไม่เหมาะที่จะใส่เอฟเฟกต์ศิลป์ลงใน TTY
    สาเหตุของ error ไม่ว่าอย่างไรก็คือ jaq ไม่มี strftime

  • ความประทับใจแรกคือมี error message ที่สวย แต่ไม่มี halt_error/0
    หลังจากคอมเมนต์ halt_error ออกแล้ว ก็ยังช้ากว่า jq และ gojq
    บน input เดียวกัน jq ใช้เวลาประมาณ 0.023 วินาที, gojq ประมาณ 0.070 วินาที, jaq ประมาณ 0.103 วินาที
    aoc22-13.jq ที่ใช้คือ https://pastebin.com/raw/YiUjEu2n และ input.txt คือ https://pastebin.com/raw/X0FSyTNf

  • เริ่มใช้ yq แทน jq แล้ว อยากรู้ว่ามีความแตกต่างสำคัญอะไรไหม

    • ขึ้นอยู่กับว่าเป็น yq ตัวไหน
      ส่วนตัวชอบ https://github.com/mikefarah/yq มากกว่า https://github.com/kislyuk/yq
    • รู้สึกว่า jq เป็นเครื่องมือที่มั่นคงกว่า yq มาก
      เข้าใจว่าการจัดการ YAML ยากกว่า JSON มาก แต่ถึง yq จะเปลี่ยน syntax จากเวอร์ชัน 3 ไป 4 ให้ใกล้กับ jq มากขึ้น ก็ยังรู้สึกว่าไม่เหมือนกันทั้งหมดอยู่ดี
      อีกอย่าง yq ไม่มี if-then-else จึงดูเหมือนออกแบบไม่ดีหรือพลาดไป: https://github.com/mikefarah/yq/issues/95
      เมื่อต้องจัดการ YAML yq ทำงานได้ดีและจัดการคอมเมนต์ได้ค่อนข้างดี แต่สำหรับการประมวลผล JSON ล้วน ๆ jq เป็นเครื่องมือที่ดีกว่า