- Ein Rust-CLI-Tool zum pfadbasierten Durchsuchen von JSON-Dokumenten, das bei der Suchgeschwindigkeit schneller ist als
jq, jmespath, jsonpath-rust und jql
- Es drückt Abfragen als reguläre Sprache aus und kompiliert sie zu einem DFA; die JSON-Baumstruktur wird in einem einzigen Durchlauf durchsucht, wodurch die Verarbeitung in O(n) erfolgt
- Verwendet
serde_json_borrow mit Unterstützung für Zero-Copy-Parsing, um Speicherallokationen zu minimieren, und ist nach der Performance-Philosophie von ripgrep entworfen
- Benchmark-Ergebnisse zeigen die beste End-to-End-Performance auch bei großen JSON-Dateien; dazu kommt eine einfache, auf Suche fokussierte Abfragesprache
- Unter der MIT-Lizenz veröffentlicht; die DFA-basierte Query-Engine kann auch als Rust-Bibliothek wiederverwendet werden
Überblick über jsongrep
- jsongrep ist ein Rust-basiertes CLI-Tool, das Werte in JSON-Dokumenten pfadbasiert sucht und auf höhere Geschwindigkeit als
jq, jmespath, jsonpath-rust und jql abzielt
- JSON-Dokumente werden als Baum betrachtet; Pfade werden als reguläre Sprache ausgedrückt, in einen DFA (Deterministic Finite Automaton) kompiliert und dann in einem einzigen Durchlauf durchsucht
- Die Abfragesprache ist einfach und suchorientiert gestaltet; Transformations- oder Rechenfunktionen gibt es nicht
- Zero-Copy-Parsing mit
serde_json_borrow minimiert Speicherallokationen
- Entwickelt mit Bezug auf die Design-Philosophie und den Performance-Ansatz von
ripgrep
Beispiele für die Verwendung von jsongrep
- Der Befehl
jg nimmt eine Abfrage und JSON-Eingabe entgegen und gibt alle Werte aus, deren Pfad zur Abfrage passt
- Zugriff auf verschachtelte Felder mit Punktnotation (dot path)
jg 'roommates[0].name' → "Alice"
- Wildcards (
*, [*]) zum Abgleichen aller Schlüssel oder Indizes
- Alternation (
|) zur Auswahl eines von mehreren Pfaden
- Rekursive Suche (
(* | [*])*) für die Feldsuche in beliebiger Tiefe
- Optional (
?) unterstützt 0- oder 1-maliges Matching
- Mit der Option
-F kann schnell nach einem bestimmten Feldnamen gesucht werden
- Bei Verwendung von Pipes (
| less, | sort) wird die Pfadausgabe automatisch unterdrückt; mit --with-path lässt sie sich erzwingen
Zentrale Konzepte von jsongrep
- JSON ist eine Baumstruktur, in der Objektschlüssel und Array-Indizes als Kanten (edges) fungieren
- Eine Abfrage definiert eine Menge von Pfaden vom Root zu bestimmten Knoten
- Die Abfragesprache ist als reguläre Sprache entworfen und kann daher in einen DFA umgewandelt werden
- Der DFA liest die Eingabe nur einmal und durchsucht sie ohne Backtracking in O(n)
- Bestehende Tools (
jq, jmespath usw.) interpretieren Abfragen und durchsuchen rekursiv, während jsongrep mit einem vorab kompilierten DFA in einem einzigen Durchlauf sucht
Aufbau der DFA-basierten Query-Engine
- Die Pipeline besteht aus 5 Schritten
- JSON mit
serde_json_borrow in einen Baum parsen
- Die Abfrage in einen AST parsen
- Mit dem Glushkov-Algorithmus einen NFA erzeugen
- Per Subset Construction in einen DFA umwandeln
- Den JSON-Baum in einer einzelnen DFS entlang der DFA-Übergänge durchsuchen
-
Parsing der Abfragen
- Mit einer PEG-Grammatik (unter Verwendung der Bibliothek
pest) wird die Abfrage in einen Query-AST umgewandelt
- Wichtige Syntaxelemente:
Field, Index, Range, FieldWildcard, ArrayWildcard, Optional, KleeneStar, Disjunction, Sequence
- Beispiel:
roommates[*].name → Sequence(Field("roommates"), ArrayWildcard, Field("name"))
-
JSON-Baummodell
- Objektschlüssel und Array-Indizes sind Kanten, Werte sind Knoten
- Beispiel:
roommates[*].name durchsucht den Pfad roommates → [0] → name
-
NFA-Konstruktion (Glushkov-Algorithmus)
- Erzeugt einen NFA ohne ε-Übergänge
- Schritte
- Positionsnummern für die Abfragesymbole vergeben
- First-/Last-/Follows-Mengen berechnen
- Übergänge zwischen den Positionen aufbauen
- Das Beispiel
roommates[*].name ergibt einen einfachen linearen NFA mit 4 Zuständen
-
DFA-Umwandlung (Subset Construction)
- Erzeugt einen deterministischen DFA auf Basis von Mengen von NFA-Zuständen
- Jeder Zustand entspricht einer Menge von NFA-Zuständen
- Ein zusätzliches Symbol
Other erlaubt es, irrelevante Schlüssel effizient zu überspringen
- Einfache Abfragen werden in einen DFA mit derselben Struktur wie der NFA umgewandelt
-
DFS-basierte Suche
- Beginnend am Root werden entlang jeder Kante DFA-Übergänge ausgeführt
- Gibt es keinen Übergang, wird der betreffende Teilbaum abgeschnitten (pruned)
- Ist ein DFA-Zustand accepting, werden Pfad und Wert aufgezeichnet
- Jeder Knoten wird höchstens einmal besucht; die gesamte Suche ist O(n)
- Mit
serde_json_borrow wird ohne String-Kopien direkt auf den Originalpuffer referenziert
Benchmark-Methodik
- Statistikbasierte Benchmarks mit Criterion.rs
-
Datensätze
simple.json (106B), kubernetes-definitions.json (~992KB), kestra-0.19.0.json (~7.6MB), citylots.json (~190MB)
-
Vergleichstools
jsongrep, jsonpath-rust, jmespath, jaq, jql
-
Benchmark-Gruppen
document_parse: Geschwindigkeit beim JSON-Parsing
query_compile: Zeit für die Query-Kompilierung
query_search: nur die Suche
end_to_end: die gesamte Pipeline
-
Aspekte der Fairness
- Der Vorteil von Zero-Copy-Parsing wird separat gemessen
- Die Kosten der DFA-Kompilierung werden getrennt gemessen
- Tools ohne entsprechende Funktion werden von dem jeweiligen Test ausgeschlossen
- Die Kosten für Datenkopien werden separat behandelt
Benchmark-Ergebnisse
- Zeit fürs Dokument-Parsing:
serde_json_borrow ist am schnellsten
- Zeit für die Query-Kompilierung:
jsongrep hat wegen der DFA-Erzeugung die höchsten Kosten, jmespath ist deutlich schneller
- Suchzeit:
jsongrep ist unter allen Tools am schnellsten
- End-to-End-Performance: Selbst beim 190-MB-Datensatz deutlich schneller als
jq, jmespath, jsonpath-rust und jql
- Die vollständigen Ergebnisse sind auf der Live-Benchmark-Seite verfügbar
Lizenz und Einsatzmöglichkeiten
- Open-Source-Software unter der MIT-Lizenz
- Verfügbar über GitHub, Crates.io und Docs.rs
- Die DFA-basierte Query-Engine kann als Bibliothek wiederverwendet und direkt in Rust-Projekte integriert werden
Literaturhinweise
- Glushkov, V. M. (1961), The Abstract Theory of Automata
- Rabin, M. O., & Scott, D. (1959), Finite Automata and Their Decision Problems
3 Kommentare
Ziemlich cool.
| Warum wird das Pipe-Zeichen im Haupttext anders angezeigt? Irgendwie interessant..
Hacker-News-Kommentare
Die Syntax von jq ist zu kryptisch, sodass ich jedes Mal nachschlagen muss, selbst wenn ich nur einen einfachen JSON-Wert herausziehen will
Ich schreibe meist Einweg-Filter, daher verbringe ich mehr Zeit mit dem Schreiben als mit dem Lesen
Wahrscheinlich sind meine Anwendungsfälle einfach oder jq passt einfach gut zu meiner Denkweise
Ich träume von einer Welt, in der alle CLI-Tools JSON ein- und ausgeben und über jq verbunden werden, aber für dich klingt das vermutlich wie ein Albtraum
Jedes Mal, wenn man es verwendet, muss man es praktisch neu lernen, also ist es nicht intuitiv
Auch
sedist Turing-vollständig, trotzdem nutzen die meisten es nur für Regex-ErsetzungenIch mag jq, aber es gab Zeiten, in denen ich Abfragen, die ich früher selbst geschrieben hatte, nicht mehr verstanden habe
celq verwendet die vertrautere CEL-Sprache
Es ist einfach ein Weg, JSON mit JavaScript zu verarbeiten, und überraschenderweise ist es schneller als jq
Verwendet wird es etwa so:
$ cat package.json | dq 'Object.keys(data).slice(0, 5)'Da ich Clojure gelernt habe, nutze ich inzwischen statt JSON EDN
Es ist kompakter, leichter zu lesen und strukturell einfacher zu verarbeiten
Heute arbeite ich mit Daten über borkdude/jet oder babashka und visualisiere sie mit djblue/portal
Ich verstehe nicht, warum man auf den komplizierten Operatoren von jq beharren sollte
Leistung ist mir wichtig, aber Vergleiche im Nanosekundenbereich wirken auf mich wie Performance-Theater
In den meisten Fällen reicht das aktuelle Tool vollkommen aus
Zum Beispiel nutze ich bei großen Dateien statt grep nur dann rg
Der Unterschied zwischen 2 ms und 0,2 ms wirkt gering, ist aber für Leute, die Streams im TB-Bereich verarbeiten, relevant
Die Hardware ist schneller geworden, aber die Software in der Realität eher langsamer
Sich Optimierung zu verweigern, wirkt wie Faulheit und mangelnde Vorstellungskraft
Sich damit zu beruhigen, dass etwas immer noch schneller als Netzwerklatenz ist, klingt nach einer Ausrede
Wenn JSON zu groß ist, sollte man statt JSON besser ein Binärformat verwenden
Und wenn man in der CLI komplexe Pipelines bauen muss, ist es womöglich besser, gleich ein Programm zu schreiben
Viele neue CLI-Tools werben mit „schneller“, aber ich hatte nur selten das Gefühl, dass jq tatsächlich langsam ist
Selbst einfache Aufgaben wie das Umbenennen von Feldnamen sind mit jq zu langsam, daher verarbeite ich sie direkt mit Node- oder Rust-Skripten
In Hyperscaler-Umgebungen lädt man mehrere TB an Logs herunter und analysiert sie direkt
Je nach Auflösung des Monitorings kann der Leistungsunterschied spürbar sein
Oft wird nur ein Teil der Funktionen implementiert und dann mit Benchmarks der Sieg verkündet
Auch dieses Projekt wirkt wie Teil dieses Trends „ein Teilmengen-Tool ist schneller“
Danach fühlt sich plötzlich alles langsam an
Wenn man einmal ein schnelles Tool wie ripgrep benutzt hat, ist der Weg zurück schwer
Ich habe sowohl jq als auch yq benutzt, aber bei yq hat mich die deutlich geringere Geschwindigkeit nie gestört
Ein Tool, das schneller als jq ist, ist nett, aber das braucht nur eine bestimmte Nutzergruppe
Trotzdem zolle ich als jemand, der Optimierung liebt, Respekt
Im ETL-Schritt kostet das durchaus Zeit
Beim ersten Öffnen der Seite gab es einen kaputten Light-Mode-Farbstil
Nach dem Wechsel in den Dark Mode und zurück war es behoben
Ich bin aus Gründen der Korrektheit zu Jaq gewechselt
Angeblich ist es auch schneller als jq
Der Ruf von jq als langsames Tool scheint eher an Problemen beim Paketieren in Distributionen zu liegen
Ich arbeite beruflich oft mit newline-delimited JSON (jsonl)
Jede Zeile ist ein vollständiges JSON-Objekt, und ich frage mich, ob die wichtigsten CLI-Tools dieses Format unterstützen
Ich habe viele CLI-Tools zur Datenverarbeitung benutzt, darunter jq, mlr, htmlq, xsv und yq,
aber seit ich Nushell entdeckt habe, hat es sie alle ersetzt
Ein einziges Syntaxsystem für alle Formate zu haben, war eine erfrischende Erfahrung
Nur bei der Zusammenarbeit mit Kolleginnen und Kollegen verwende ich zusätzlich jq, yq und mlr
Bei der Einrichtung der Autovervollständigung und der Auffindbarkeit von Befehlen gibt es etwas Reibung, aber es ist viel besser als oh-my-zsh
Wenn noch erzwungene Typannotationen, statisch kompilierte Binärdateien und eine TUI-Bibliothek dazukommen, würde ich es sogar für kleine Apps nutzen
Tolles Tool! Allerdings ist die Benchmark-Visualisierung etwas enttäuschend
Alle Tools haben dieselbe Farbe, sodass es schwer ist, jsongrep darin zu finden
jq selbst fehlt auch in der Grafik, was verwirrend war
Die xLarge-Datei ist mit 190 MiB eher klein; ich arbeite oft mit JSON-Dateien von 400 MiB bis 1 GiB
Wenn es größere öffentliche JSON-Dokumente gibt, wäre ich für Hinweise dankbar
Die Benchmark-Visualisierung wirkt grob
Es wäre gut, über Farben oder Formen mehr Dimensionen darzustellen
Dass man die Dateipfade direkt lesen muss, um die Ergebnisse zu verstehen, ist unpraktisch