- เป็นเครื่องมือ 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 ขั้นตอน
- แยกวิเคราะห์ JSON เป็นต้นไม้ด้วย
serde_json_borrow
- แยกวิเคราะห์คิวรีเป็น AST
- สร้าง NFA ด้วยอัลกอริทึม Glushkov
- แปลงเป็น DFA ด้วย Subset Construction
- สำรวจต้นไม้ JSON ด้วย DFS เดียวตาม transition ของ DFA
-
การแยกวิเคราะห์คิวรี
- ใช้ไวยากรณ์ PEG (ผ่านไลบรารี
pest) เพื่อแปลงคิวรีเป็น Query AST
- องค์ประกอบไวยากรณ์หลัก:
Field, Index, Range, FieldWildcard, ArrayWildcard, Optional, KleeneStar, Disjunction, Sequence
- ตัวอย่าง:
roommates[*].name → Sequence(Field("roommates"), ArrayWildcard, Field("name"))
-
โมเดลต้นไม้ JSON
- คีย์ของอ็อบเจ็กต์และดัชนีของอาร์เรย์คือ ขอบ (edge) ส่วนค่าคือ โหนด
- ตัวอย่าง:
roommates[*].name จะสำรวจเส้นทาง roommates → [0] → name
-
การสร้าง NFA (อัลกอริทึม Glushkov)
- สร้าง NFA ที่ไม่มี ε-transition
- ขั้นตอน
- กำหนดหมายเลขตำแหน่งให้กับสัญลักษณ์ในคิวรี
- คำนวณชุด First/Last/Follows
- สร้าง 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
-
กลุ่มเบนช์มาร์ก
document_parse: ความเร็วในการแยกวิเคราะห์ JSON
query_compile: เวลาในการคอมไพล์คิวรี
query_search: ทำเฉพาะการค้นหา
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 ความคิดเห็น
เจ๋งมากเลย
| ทำไมเครื่องหมายไปป์ถึงแสดงต่างกันในเนื้อหานะ? แปลกดีครับ..
ความเห็นจาก Hacker News
ไวยากรณ์ของ jq เข้าใจยากเกินไป จนแม้แต่จะดึงค่า JSON ง่าย ๆ แค่ค่าเดียวก็ยังต้องค้นหาทุกครั้ง
ปกติฉันเขียนฟิลเตอร์แบบใช้ครั้งเดียวเป็นหลัก เลยใช้เวลาเขียนมากกว่าเวลาอ่าน
อาจเป็นเพราะกรณีใช้งานของฉันค่อนข้างง่าย หรือ jq เข้ากับวิธีคิดของฉันพอดี
ฉันฝันถึงโลกที่เครื่องมือ CLI ทุกตัวรับเข้าและส่งออก JSON แล้วต่อกันด้วย jq ได้หมด แต่สำหรับคุณมันคงเป็นฝันร้าย
ทุกครั้งที่ใช้ต้องมาเรียนใหม่ เลยไม่รู้สึกว่ามันใช้งานได้อย่างเป็นธรรมชาติ
แม้ sed จะ Turing-complete แต่คนส่วนใหญ่ก็ใช้แค่แทนที่ด้วย regex เท่านั้น
ฉันชอบ jq แต่ก็เคยมีช่วงที่อ่าน query ที่ตัวเองเขียนไว้ก่อนหน้านี้ไม่ออก
celq ใช้ ภาษา CEL ที่คุ้นเคยกว่า
มันเป็นวิธี จัดการ JSON ด้วย JavaScript ตรง ๆ และน่าประหลาดที่มันเร็วกว่า jq
ใช้ประมาณนี้:
$ cat package.json | dq 'Object.keys(data).slice(0, 5)'เพราะฉันได้เรียน Clojure ตอนนี้เลยใช้ EDN แทน JSON
มันกระชับกว่า อ่านง่ายกว่า และจัดการเชิงโครงสร้างได้ง่ายกว่า
ช่วงนี้ฉันใช้ borkdude/jet หรือ babashka จัดการข้อมูล และใช้ djblue/portal สำหรับการแสดงผล
ฉันไม่เข้าใจว่าทำไมต้องยึดติดกับโอเปอเรเตอร์ซับซ้อนของ jq
ฉันให้ความสำคัญกับประสิทธิภาพ แต่การเปรียบเทียบระดับนาโนวินาทีให้ความรู้สึกเหมือน performance ที่ทำไว้โชว์
ในกรณีส่วนใหญ่เครื่องมือที่ใช้อยู่ตอนนี้ก็เพียงพอแล้ว
ตัวอย่างเช่น ฉันจะใช้ rg แทน grep เฉพาะตอนจัดการไฟล์ใหญ่ ๆ เท่านั้น
ความต่างระหว่าง 2ms กับ 0.2ms อาจดูเล็กน้อย แต่สำหรับคนที่ประมวลผลสตรีมระดับ TB มันสำคัญ
ฮาร์ดแวร์เร็วขึ้น แต่ซอฟต์แวร์กลับช้าลงเสียอย่างนั้น
การปฏิเสธการปรับแต่งประสิทธิภาพให้ดีขึ้นฟังดูเหมือน ความขี้เกียจและการขาดจินตนาการ
การอ้างว่ามันเร็วกว่าความหน่วงเครือข่ายเลยไม่ต้องสนใจ ฟังดูเป็นข้อแก้ตัว
ถ้า JSON ใหญ่มาก ก็ควรใช้ ฟอร์แมตไบนารี แทน JSON
ถ้าต้องประกอบไปป์ไลน์ซับซ้อนบน CLI ก็เห็นว่าควรเขียนโปรแกรมไปเลยจะดีกว่า
เครื่องมือ CLI ใหม่ ๆ จำนวนมากชูจุดขายว่า “เร็วกว่า” แต่จริง ๆ แล้วฉันแทบไม่เคยรู้สึกว่า jq ช้าเลย
แม้แต่งานง่าย ๆ อย่างเปลี่ยนชื่อฟิลด์ด้วย jq ก็ยังช้าเกินไป เลยจัดการเองด้วย สคริปต์ Node หรือ Rust
ในสภาพแวดล้อมของ hyperscaler จะดาวน์โหลดล็อกระดับหลาย TB มาวิเคราะห์โดยตรง
แล้วแต่ความละเอียดของการมอนิเตอร์ว่าความต่างของประสิทธิภาพจะรู้สึกได้มากแค่ไหน
ทำฟีเจอร์มาแค่บางส่วนแล้วอ้างชัยชนะจาก benchmark
โปรเจกต์นี้ก็ดูเป็นส่วนหนึ่งของกระแสแบบ “subset เร็วกว่า” เช่นกัน
หลังจากนั้นทุกอย่างจะเริ่มรู้สึกช้าไปหมด
เหมือน ripgrep ที่พอใช้เครื่องมือที่เร็วแล้ว ก็ยากจะกลับไปใช้ของเดิม
ฉันเคยใช้ทั้ง jq และ yq แต่ yq ช้ากว่ามากก็ยังไม่เคยบ่น
ถ้ามีเครื่องมือที่เร็วกว่า jq ก็ดีนะ แต่คงจำเป็นกับ ผู้ใช้บางกลุ่มเท่านั้น
ถึงอย่างนั้น ในฐานะคนที่รักการ optimization ก็ขอแสดงความนับถือ
ในขั้นตอน ETL ใช้เวลาพอสมควร
ตอนเปิดหน้าเว็บครั้งแรกมีปัญหา สีของโหมดสว่างเพี้ยน
แต่ถ้าสลับไปโหมดมืดแล้วค่อยกลับมาก็หาย
ฉันเปลี่ยนไปใช้ Jaq เพราะเรื่อง ความถูกต้องแม่นยำ
และบอกกันว่าประสิทธิภาพก็ดีกว่า jq ด้วย
ชื่อเสียเรื่องความช้าของ jq ดูเหมือนจะมาจากปัญหาแพ็กเกจของดิสโทร
ในงานฉันต้องจัดการ newline-delimited JSON (jsonl) บ่อยมาก
แต่ละบรรทัดเป็นออบเจ็กต์ JSON ที่สมบูรณ์ เลยสงสัยว่าเครื่องมือ CLI หลัก ๆ รองรับฟอร์แมตนี้ไหม
ฉันเคยใช้ เครื่องมือ CLI สำหรับประมวลผลข้อมูล หลายตัว เช่น jq, mlr, htmlq, xsv, yq
แต่หลังจากเจอ Nushell ก็แทนที่ทั้งหมดได้เลย
การจัดการทุกฟอร์แมตด้วยไวยากรณ์เดียวเป็นประสบการณ์ที่สดใหม่มาก
จะใช้ jq, yq, mlr ควบคู่กันก็ตอนต้องทำงานร่วมกับเพื่อนร่วมทีมเท่านั้น
ยังมีจุดไม่สะดวกเล็กน้อยเรื่องการตั้งค่า autocomplete และความสามารถในการค้นหาคำสั่ง แต่ก็ ดีกว่า oh-my-zsh มาก
ถ้ามีการบังคับใช้ type annotation, คอมไพล์เป็น static binary, และมีไลบรารี TUI เพิ่ม ก็น่าจะใช้เขียนแอปเล็ก ๆ ได้ด้วย
เป็นเครื่องมือที่ดี! แต่รู้สึกว่า การแสดงผล benchmark ยังน่าเสียดายนิดหน่อย
ทุกเครื่องมือใช้สีเดียวกัน เลยหาว่า jsongrep อยู่ตรงไหนได้ยาก
ตัว jq เองก็ไม่มีในกราฟ เลยยิ่งสับสน
ไฟล์ xLarge ขนาด 190MiB ยังถือว่าเล็กไปหน่อย เพราะฉันทำงานกับ JSON ขนาด 400MiB~1GiB อยู่บ่อย
ถ้ามีเอกสาร JSON สาธารณะขนาดใหญ่กว่านี้ก็ช่วยบอกได้
รู้สึกว่า การแสดงผล benchmark ยังหยาบไปหน่อย
ถ้าใช้สีหรือรูปทรงช่วยสื่อหลายมิติได้มากขึ้นก็น่าจะดี
การที่ต้องอ่านพาธของไฟล์เองเพื่อทำความเข้าใจผลลัพธ์ค่อนข้างไม่สะดวก