17 คะแนน โดย GN⁺ 2026-03-28 | 3 ความคิดเห็น | แชร์ทาง WhatsApp
  • เป็นเครื่องมือ CLI ภาษา Rust สำหรับ ค้นหาเอกสาร JSON ตามเส้นทาง โดยมี ความเร็วในการค้นหา สูงกว่า jq, jmespath, jsonpath-rust, jql ที่มีอยู่เดิม
  • แสดงคิวรีเป็น ภาษาปรกติ (regular language) และคอมไพล์เป็น DFA จากนั้นสำรวจต้นไม้ JSON แบบ single pass จึงประมวลผลได้ในเวลา O(n)
  • ใช้ serde_json_borrow ที่รองรับ zero-copy parsing เพื่อลดการจัดสรรหน่วยความจำให้ต่ำที่สุด และออกแบบโดยอ้างอิงแนวคิดด้านประสิทธิภาพของ ripgrep
  • ผลเบนช์มาร์กแสดงให้เห็นว่าแม้กับ JSON ขนาดใหญ่ก็ยังมี ประสิทธิภาพแบบ end-to-end ดีที่สุด พร้อมภาษาคิวรีแบบเรียบง่ายที่เน้นการค้นหา
  • เผยแพร่ภายใต้ MIT License และสามารถ นำเอนจินคิวรีแบบ DFA กลับมาใช้ซ้ำเป็นไลบรารี Rust ได้

ภาพรวมของ jsongrep

  • jsongrep เป็น เครื่องมือ CLI ที่พัฒนาด้วย Rust สำหรับค้นหาค่าในเอกสาร JSON ตามเส้นทาง โดยมีเป้าหมายด้านประสิทธิภาพที่เร็วกกว่า jq, jmespath, jsonpath-rust, jql
  • มองเอกสาร JSON เป็นต้นไม้ และแสดง เส้นทาง (path) เป็น ภาษาปรกติ (regular language) ก่อนคอมไพล์เป็น DFA (Deterministic Finite Automaton) แล้วทำการสำรวจแบบ single pass
  • ภาษาคิวรีเรียบง่ายและออกแบบมาโดย เน้นการค้นหา จึงไม่มีความสามารถด้านการแปลงหรือการคำนวณ
  • ลดการจัดสรรหน่วยความจำให้ต่ำที่สุดด้วย zero-copy parsing ผ่าน serde_json_borrow
  • พัฒนาโดยอ้างอิงแนวคิดการออกแบบและแนวทางด้านประสิทธิภาพของ ripgrep

ตัวอย่างการใช้งาน jsongrep

  • คำสั่ง jg รับ คิวรี และ อินพุต JSON แล้วแสดงค่าทั้งหมดที่มีเส้นทางตรงกับคิวรี
  • ใช้ dot path notation เพื่อเข้าถึงฟิลด์ที่ซ้อนกัน
    • jg 'roommates[0].name'"Alice"
  • ใช้ wildcard (*, [*]) เพื่อจับคู่ทุกคีย์หรือทุกดัชนี
  • ใช้ Alternation (|) เพื่อเลือกหนึ่งในหลายเส้นทาง
  • ใช้ การค้นหาแบบ recursive ((* | [*])*) เพื่อค้นหาฟิลด์ในความลึกใดก็ได้
  • ใช้ Optional (?) เพื่อรองรับการจับคู่ 0 หรือ 1 ครั้ง
  • ใช้ตัวเลือก -F เพื่อค้นหาชื่อฟิลด์เฉพาะได้อย่างรวดเร็ว
  • เมื่อใช้ pipe (| less, | sort) ระบบจะไม่แสดงเส้นทางโดยอัตโนมัติ และสามารถบังคับให้แสดงได้ด้วย --with-path

แนวคิดหลักของ jsongrep

  • JSON คือ โครงสร้างต้นไม้ และคีย์ของอ็อบเจ็กต์กับดัชนีของอาร์เรย์ทำหน้าที่เป็น ขอบ (edge)
  • คิวรีนิยาม ชุดของเส้นทาง จากรากไปยังโหนดเป้าหมาย
  • ภาษาคิวรีถูกออกแบบเป็น ภาษาปรกติ จึงสามารถแปลงเป็น DFA ได้
  • DFA อ่านอินพุตเพียงครั้งเดียวและสำรวจได้ในเวลา O(n) โดย ไม่ต้อง backtracking
  • เครื่องมือเดิม (jq, jmespath เป็นต้น) จะ ตีความคิวรี และสำรวจแบบ recursive แต่ jsongrep ใช้ DFA ที่คอมไพล์ไว้ล่วงหน้า เพื่อสำรวจแบบ single pass

โครงสร้างเอนจินคิวรีแบบ DFA

  • ไปป์ไลน์ประกอบด้วย 5 ขั้นตอน
    1. แยกวิเคราะห์ JSON เป็นต้นไม้ด้วย serde_json_borrow
    2. แยกวิเคราะห์คิวรีเป็น AST
    3. สร้าง NFA ด้วยอัลกอริทึม Glushkov
    4. แปลงเป็น DFA ด้วย Subset Construction
    5. สำรวจต้นไม้ JSON ด้วย DFS เดียวตาม transition ของ DFA
  • การแยกวิเคราะห์คิวรี

    • ใช้ไวยากรณ์ PEG (ผ่านไลบรารี pest) เพื่อแปลงคิวรีเป็น Query AST
    • องค์ประกอบไวยากรณ์หลัก: Field, Index, Range, FieldWildcard, ArrayWildcard, Optional, KleeneStar, Disjunction, Sequence
    • ตัวอย่าง: roommates[*].nameSequence(Field("roommates"), ArrayWildcard, Field("name"))
  • โมเดลต้นไม้ JSON

    • คีย์ของอ็อบเจ็กต์และดัชนีของอาร์เรย์คือ ขอบ (edge) ส่วนค่าคือ โหนด
    • ตัวอย่าง: roommates[*].name จะสำรวจเส้นทาง roommates[0]name
  • การสร้าง NFA (อัลกอริทึม Glushkov)

    • สร้าง NFA ที่ไม่มี ε-transition
    • ขั้นตอน
      1. กำหนดหมายเลขตำแหน่งให้กับสัญลักษณ์ในคิวรี
      2. คำนวณชุด First/Last/Follows
      3. สร้าง transition ระหว่างแต่ละตำแหน่ง
    • ตัวอย่างคิวรี roommates[*].name มี NFA เป็นโครงสร้างเชิงเส้นอย่างง่ายที่ประกอบด้วย 4 สถานะ
  • การแปลงเป็น DFA (Subset Construction)

    • สร้าง DFA แบบกำหนดแน่นอน จากชุดสถานะของ NFA
    • แต่ละสถานะสอดคล้องกับชุดสถานะหนึ่งชุดของ NFA
    • เพิ่มสัญลักษณ์ Other เพื่อข้ามคีย์ที่ไม่จำเป็นได้อย่างมีประสิทธิภาพ
    • คิวรีแบบง่ายจะถูกแปลงเป็น DFA ที่มีโครงสร้างเหมือนกับ NFA
  • การสำรวจแบบ DFS

    • เริ่มจากรากและทำ transition ของ DFA ไปตามแต่ละขอบ
    • หากไม่มี transition จะ ตัดกิ่ง (prune) ของ subtree นั้น
    • หากสถานะ DFA เป็น accepting จะบันทึกเส้นทางและค่า
    • แต่ละโหนดจะถูกเยี่ยมชมได้ไม่เกินหนึ่งครั้ง และการสำรวจทั้งหมดเป็น O(n)
    • serde_json_borrow อ้างอิงบัฟเฟอร์ต้นฉบับโดยไม่ต้องคัดลอกสตริง

ระเบียบวิธีของเบนช์มาร์ก

  • ทำเบนช์มาร์กเชิงสถิติด้วย Criterion.rs
  • ชุดข้อมูล

    • simple.json (106B), kubernetes-definitions.json (~992KB), kestra-0.19.0.json (~7.6MB), citylots.json (~190MB)
  • เครื่องมือที่ใช้เปรียบเทียบ

    • jsongrep, jsonpath-rust, jmespath, jaq, jql
  • กลุ่มเบนช์มาร์ก

    1. document_parse: ความเร็วในการแยกวิเคราะห์ JSON
    2. query_compile: เวลาในการคอมไพล์คิวรี
    3. query_search: ทำเฉพาะการค้นหา
    4. end_to_end: ไปป์ไลน์ทั้งหมด
  • ข้อพิจารณาเรื่องความเป็นธรรม

    • วัดข้อได้เปรียบของ zero-copy parsing แยกต่างหาก
    • วัดต้นทุนการคอมไพล์ DFA แยกต่างหาก
    • เครื่องมือที่ไม่มีฟังก์ชันที่เกี่ยวข้องจะถูกยกเว้นจากการทดสอบนั้น
    • แยกต้นทุนของการคัดลอกข้อมูลออกต่างหาก

ผลเบนช์มาร์ก

  • เวลาแยกวิเคราะห์เอกสาร: serde_json_borrow เร็วที่สุด
  • เวลาในการคอมไพล์คิวรี: jsongrep มีต้นทุนสูงสุดจากการสร้าง DFA ขณะที่ jmespath เร็วกว่าอย่างมาก
  • เวลาในการค้นหา: jsongrep เร็วที่สุดในบรรดาเครื่องมือทั้งหมด
  • ประสิทธิภาพแบบ end-to-end: แม้กับชุดข้อมูลขนาด 190MB ก็ยัง เร็วกว่าอย่างทิ้งห่าง เมื่อเทียบกับ jq, jmespath, jsonpath-rust, jql
  • ดูผลทั้งหมดได้ที่ เว็บไซต์เบนช์มาร์กแบบสด

ใบอนุญาตและการนำไปใช้

  • เป็นซอฟต์แวร์โอเพนซอร์สภายใต้ MIT License
  • ใช้งานได้ผ่าน GitHub, Crates.io และ Docs.rs
  • สามารถ นำเอนจินคิวรีแบบ DFA กลับมาใช้ซ้ำในรูปแบบไลบรารี และรวมเข้ากับโปรเจกต์ Rust ได้โดยตรง

เอกสารอ้างอิง

  • Glushkov, V. M. (1961), The Abstract Theory of Automata
  • Rabin, M. O., & Scott, D. (1959), Finite Automata and Their Decision Problems

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

 
roxie 26 일 전

เจ๋งมากเลย

 
lamanus 2026-03-28

| ทำไมเครื่องหมายไปป์ถึงแสดงต่างกันในเนื้อหานะ? แปลกดีครับ..

 
GN⁺ 2026-03-28
ความเห็นจาก Hacker News
  • ไวยากรณ์ของ jq เข้าใจยากเกินไป จนแม้แต่จะดึงค่า JSON ง่าย ๆ แค่ค่าเดียวก็ยังต้องค้นหาทุกครั้ง

    • น่าสนใจนะ สำหรับฉันไวยากรณ์ของ jq ค่อนข้างตรงไปตรงมา และก็คุ้นกับการใช้แค่จุด, pipe, และวงเล็บเหลี่ยมแบบ เชลล์ไปป์ไลน์
      ปกติฉันเขียนฟิลเตอร์แบบใช้ครั้งเดียวเป็นหลัก เลยใช้เวลาเขียนมากกว่าเวลาอ่าน
      อาจเป็นเพราะกรณีใช้งานของฉันค่อนข้างง่าย หรือ jq เข้ากับวิธีคิดของฉันพอดี
      ฉันฝันถึงโลกที่เครื่องมือ CLI ทุกตัวรับเข้าและส่งออก JSON แล้วต่อกันด้วย jq ได้หมด แต่สำหรับคุณมันคงเป็นฝันร้าย
    • jq เป็นเครื่องมือที่ไม่ได้ใช้บ่อย เลยติดอยู่ใน “หุบเขาแห่งการเรียนรู้”
      ทุกครั้งที่ใช้ต้องมาเรียนใหม่ เลยไม่รู้สึกว่ามันใช้งานได้อย่างเป็นธรรมชาติ
      แม้ sed จะ Turing-complete แต่คนส่วนใหญ่ก็ใช้แค่แทนที่ด้วย regex เท่านั้น
    • ขอแนะนำ celq ที่ฉันทำขึ้นมา
      ฉันชอบ jq แต่ก็เคยมีช่วงที่อ่าน query ที่ตัวเองเขียนไว้ก่อนหน้านี้ไม่ออก
      celq ใช้ ภาษา CEL ที่คุ้นเคยกว่า
    • ฉันก็สร้างเครื่องมือชื่อ dq เองด้วยเหตุผลคล้ายกัน
      มันเป็นวิธี จัดการ JSON ด้วย JavaScript ตรง ๆ และน่าประหลาดที่มันเร็วกว่า jq
      ใช้ประมาณนี้: $ cat package.json | dq 'Object.keys(data).slice(0, 5)'
    • ตัว JSON เองก็มี syntax noise ที่ไม่จำเป็น เยอะจนน่ารำคาญ
      เพราะฉันได้เรียน Clojure ตอนนี้เลยใช้ EDN แทน JSON
      มันกระชับกว่า อ่านง่ายกว่า และจัดการเชิงโครงสร้างได้ง่ายกว่า
      ช่วงนี้ฉันใช้ borkdude/jet หรือ babashka จัดการข้อมูล และใช้ djblue/portal สำหรับการแสดงผล
      ฉันไม่เข้าใจว่าทำไมต้องยึดติดกับโอเปอเรเตอร์ซับซ้อนของ jq
  • ฉันให้ความสำคัญกับประสิทธิภาพ แต่การเปรียบเทียบระดับนาโนวินาทีให้ความรู้สึกเหมือน performance ที่ทำไว้โชว์
    ในกรณีส่วนใหญ่เครื่องมือที่ใช้อยู่ตอนนี้ก็เพียงพอแล้ว
    ตัวอย่างเช่น ฉันจะใช้ rg แทน grep เฉพาะตอนจัดการไฟล์ใหญ่ ๆ เท่านั้น

    • เวลาคิดแบบนี้ก็แค่บอกตัวเองว่า “ฉัน ไม่ใช่ผู้ใช้เป้าหมาย
      ความต่างระหว่าง 2ms กับ 0.2ms อาจดูเล็กน้อย แต่สำหรับคนที่ประมวลผลสตรีมระดับ TB มันสำคัญ
    • แต่ถ้าทุกคนคิดแบบนั้น สุดท้าย ความไร้ประสิทธิภาพเล็ก ๆ จะสะสม จนทั้งระบบช้าลง
      ฮาร์ดแวร์เร็วขึ้น แต่ซอฟต์แวร์กลับช้าลงเสียอย่างนั้น
    • ถ้า jq จะสร้างความแตกต่างได้ ก็ควรมี ไวยากรณ์ที่เข้าใจง่ายและตัวอย่างใช้งานจริง มากกว่านี้
    • คำว่า “เร็วพอแล้ว” ทำให้ฉันติดใจอยู่เสมอ
      การปฏิเสธการปรับแต่งประสิทธิภาพให้ดีขึ้นฟังดูเหมือน ความขี้เกียจและการขาดจินตนาการ
      การอ้างว่ามันเร็วกว่าความหน่วงเครือข่ายเลยไม่ต้องสนใจ ฟังดูเป็นข้อแก้ตัว
    • ฉันเองก็ให้ความสำคัญกับ การใช้งาน และฟีเจอร์มากกว่าความเร็ว
      ถ้า JSON ใหญ่มาก ก็ควรใช้ ฟอร์แมตไบนารี แทน JSON
      ถ้าต้องประกอบไปป์ไลน์ซับซ้อนบน CLI ก็เห็นว่าควรเขียนโปรแกรมไปเลยจะดีกว่า
  • เครื่องมือ CLI ใหม่ ๆ จำนวนมากชูจุดขายว่า “เร็วกว่า” แต่จริง ๆ แล้วฉันแทบไม่เคยรู้สึกว่า jq ช้าเลย

    • ฉันทำงานกับไฟล์ ndjson ระดับ TB
      แม้แต่งานง่าย ๆ อย่างเปลี่ยนชื่อฟิลด์ด้วย jq ก็ยังช้าเกินไป เลยจัดการเองด้วย สคริปต์ Node หรือ Rust
    • ถ้าต้องจัดการ ไฟล์ล็อกขนาดมหาศาล ก็อาจรู้สึกว่า jq ช้าได้
      ในสภาพแวดล้อมของ hyperscaler จะดาวน์โหลดล็อกระดับหลาย TB มาวิเคราะห์โดยตรง
    • เราพาร์ส JSON response จากโหนดหลายพันตัว
      แล้วแต่ความละเอียดของการมอนิเตอร์ว่าความต่างของประสิทธิภาพจะรู้สึกได้มากแค่ไหน
    • ทุกครั้งที่มีเครื่องมือใหม่ออกมา ก็มักเป็นแพตเทิร์นเดิมคือ “เอาไปทำใหม่ด้วย Rust แล้วเร็วกว่า”
      ทำฟีเจอร์มาแค่บางส่วนแล้วอ้างชัยชนะจาก benchmark
      โปรเจกต์นี้ก็ดูเป็นส่วนหนึ่งของกระแสแบบ “subset เร็วกว่า” เช่นกัน
    • เวลาที่เครื่องมือไหนช้า เราจะรู้ก็ต่อเมื่อมัน ช้าจริงในจังหวะที่ใช้งาน
      หลังจากนั้นทุกอย่างจะเริ่มรู้สึกช้าไปหมด
      เหมือน ripgrep ที่พอใช้เครื่องมือที่เร็วแล้ว ก็ยากจะกลับไปใช้ของเดิม
  • ฉันเคยใช้ทั้ง jq และ yq แต่ yq ช้ากว่ามากก็ยังไม่เคยบ่น
    ถ้ามีเครื่องมือที่เร็วกว่า jq ก็ดีนะ แต่คงจำเป็นกับ ผู้ใช้บางกลุ่มเท่านั้น
    ถึงอย่างนั้น ในฐานะคนที่รักการ optimization ก็ขอแสดงความนับถือ

    • สงสัยว่าหมายถึง yq ตัวไหน — เวอร์ชัน Go หรือเวอร์ชัน Python?
    • ในสภาพแวดล้อมรวมเซิร์ฟเวอร์ ประสิทธิภาพสำคัญ แต่บน CLI ส่วนใหญ่ก็เร็วพอแล้ว
    • ฉันใช้ jq กับ GeoJSON ขนาดหลาย GB ที่ export มาจาก ArcGIS
      ในขั้นตอน ETL ใช้เวลาพอสมควร
    • ไม่ใช่ทุกคนจะใช้เครื่องมือในแบบเดียวกัน
  • ตอนเปิดหน้าเว็บครั้งแรกมีปัญหา สีของโหมดสว่างเพี้ยน
    แต่ถ้าสลับไปโหมดมืดแล้วค่อยกลับมาก็หาย

    • ตัวเว็บเองก็ให้ความรู้สึกเหมือนถูก vibe-coded เหมือนกัน
    • ฉันเป็นคนเขียนเองและไม่ได้ใช้โหมดสว่าง เลยลืมทดสอบ เดี๋ยวจะแก้ทันที
    • ปัญหาคือสไตล์ของโหมดมืดใน CSS รั่วมาโดนบางส่วน
    • บน Android Firefox ใช้งานได้ แต่ สเกลของกราฟไม่เท่ากัน เลยเทียบกันยาก
    • ตอนนี้แก้เรียบร้อยแล้ว
  • ฉันเปลี่ยนไปใช้ Jaq เพราะเรื่อง ความถูกต้องแม่นยำ
    และบอกกันว่าประสิทธิภาพก็ดีกว่า jq ด้วย

    • ขอบคุณสำหรับคำแนะนำ jaq ดูเหมือนจะ พัฒนาไปในทิศทางที่ถูกต้องกว่า jsongrep
    • แต่ jaq 3.0 แม้จะเร็วกว่า jq เวอร์ชันจากดิสโทร แต่ jq ที่ build เอง ยังเร็วกว่า
      ชื่อเสียเรื่องความช้าของ jq ดูเหมือนจะมาจากปัญหาแพ็กเกจของดิสโทร
  • ในงานฉันต้องจัดการ newline-delimited JSON (jsonl) บ่อยมาก
    แต่ละบรรทัดเป็นออบเจ็กต์ JSON ที่สมบูรณ์ เลยสงสัยว่าเครื่องมือ CLI หลัก ๆ รองรับฟอร์แมตนี้ไหม

  • ฉันเคยใช้ เครื่องมือ CLI สำหรับประมวลผลข้อมูล หลายตัว เช่น jq, mlr, htmlq, xsv, yq
    แต่หลังจากเจอ Nushell ก็แทนที่ทั้งหมดได้เลย
    การจัดการทุกฟอร์แมตด้วยไวยากรณ์เดียวเป็นประสบการณ์ที่สดใหม่มาก

    • ฉันเองก็ใช้ Nushell เป็น เชลล์หลัก มาตั้งแต่กลางปี 2023
      จะใช้ jq, yq, mlr ควบคู่กันก็ตอนต้องทำงานร่วมกับเพื่อนร่วมทีมเท่านั้น
    • ฉันก็แทนที่ส่วนใหญ่ด้วย Nushell เหมือนกัน
      ยังมีจุดไม่สะดวกเล็กน้อยเรื่องการตั้งค่า autocomplete และความสามารถในการค้นหาคำสั่ง แต่ก็ ดีกว่า oh-my-zsh มาก
      ถ้ามีการบังคับใช้ type annotation, คอมไพล์เป็น static binary, และมีไลบรารี TUI เพิ่ม ก็น่าจะใช้เขียนแอปเล็ก ๆ ได้ด้วย
    • เห็นด้วยเลย Nushell ทำ automation ง่ายขึ้นมาก เพราะมี ไวยากรณ์ที่ตรงไปตรงมาและสม่ำเสมอ
  • เป็นเครื่องมือที่ดี! แต่รู้สึกว่า การแสดงผล benchmark ยังน่าเสียดายนิดหน่อย
    ทุกเครื่องมือใช้สีเดียวกัน เลยหาว่า jsongrep อยู่ตรงไหนได้ยาก
    ตัว jq เองก็ไม่มีในกราฟ เลยยิ่งสับสน
    ไฟล์ xLarge ขนาด 190MiB ยังถือว่าเล็กไปหน่อย เพราะฉันทำงานกับ JSON ขนาด 400MiB~1GiB อยู่บ่อย

    • ฉันเป็นผู้เขียนเองขอตอบว่า ตอนนี้ช่วง benchmark อยู่ราว 106B~190MB
      ถ้ามีเอกสาร JSON สาธารณะขนาดใหญ่กว่านี้ก็ช่วยบอกได้
  • รู้สึกว่า การแสดงผล benchmark ยังหยาบไปหน่อย
    ถ้าใช้สีหรือรูปทรงช่วยสื่อหลายมิติได้มากขึ้นก็น่าจะดี
    การที่ต้องอ่านพาธของไฟล์เองเพื่อทำความเข้าใจผลลัพธ์ค่อนข้างไม่สะดวก