- jaq ist ein Klon des JSON-Datenverarbeitungstools
jqund zielt auf eine besser vorhersehbare Implementierung ab, während in den meisten Fällen die Kompatibilität mitjqerhalten bleibt - Das Kommandozeilenprogramm
jaqkann als Drop-in-Ersatz fürjqverwendet werden, und die Rust-Bibliothekjaq-corekann jq-Programme innerhalb von Rust-Programmen kompilieren und ausführen - Es bietet Unterstützung für YAML, CBOR, TOML und XML, die es in
jqnicht gibt; außerdem lässt sichjaq-coresicher in Multithreading-Umgebungen verwenden und unterstützt beliebige Datentypen über JSON hinaus - In der Leistungsbewertung war
jaq-3.0in 20 von 31 Benchmarks am schnellsten,jq-1.8.1in 5 undgojq-0.12.18in 6 - In Bezug auf Sicherheit soll es das Vermeiden von Panics, Speichersicherheit und I/O-Beschränkungen für Eingabedaten und jq-Filter gewährleisten, reagiert jedoch nicht auf Ressourcenerschöpfung wie Zeit-, Speicher- oder Stackverbrauch
Was jaq bietet
- jaq ist ein Klon des JSON-Datenverarbeitungstools
jq; die Aussprache ist/ʒaːk/, wie bei Jacques - Es unterstützt Datenformate, die
jqnicht unterstützt-
YAML
-
CBOR
-
TOML
- XML
- Es gibt ein separates Manual und einen Playground, in dem man es ausprobieren kann
- jaq ist in zwei Formen verfügbar
- Kommandozeilenprogramm
jaq: als Drop-in-Ersatz fürjqnutzbar - Bibliothek
jaq-core: jq-Programme innerhalb von Rust-Programmen kompilier- und ausführbar
-
Designziele
-
Genauigkeit
- jaq zielt auf eine genauere und besser vorhersehbare jq-Implementierung ab und bleibt dabei in den meisten Fällen mit
jqkompatibel
- jaq zielt auf eine genauere und besser vorhersehbare jq-Implementierung ab und bleibt dabei in den meisten Fällen mit
-
Leistung
- jaq wurde ursprünglich entwickelt, weil die lange Startzeit von
jq 1.6störte; in dieser Umgebung lag die Startzeit bei etwa 50 ms - Diese Startzeit fällt besonders ins Gewicht, wenn viele kleine Dateien verarbeitet werden
- In
jq 1.7wurde die Startzeit deutlich verbessert, aber jaq ist in mehreren Benchmarks weiterhin schneller alsjq
- jaq wurde ursprünglich entwickelt, weil die lange Startzeit von
-
Einfachheit
- jaq strebt eine einfache und kleine Implementierung an, um die Wahrscheinlichkeit von Bugs zu verringern und Beiträge zu erleichtern
Installation und Build
- Binärdateien für Linux, Mac und Windows gibt es auf der Releases-Seite
- Unter macOS oder Linux kann es mit homebrew installiert werden
brew install jaqbrew install --HEAD jaq
- Zum Bauen aus dem Quellcode wird das Rust-Toolchain benötigt
cargo install --locked jaqcargo install --locked --git https://github.com/01mf02/jaq
- Wenn das Repository geklont wurde, kann mit
cargo build --releaseodercargo install --locked --path jaqgebaut bzw. installiert werden - jaq sollte auf allen Systemen laufen, die von Rust unterstützt werden; falls nicht, wird darum gebeten, ein Issue zu melden
Leistungsbewertung
- Die Leistungsbewertung besteht aus mehreren Benchmarks, die jaq, jq und gojq vergleichen
- Der
empty-Benchmark misst die Startzeit, indem derempty-Filtern-mal mit einer null-Eingabe ausgeführt wird - Der
bf-fib-Benchmark führt ein Brainfuck-Skript aus, das Fibonacci-Zahlen erzeugt, mithilfe eines in jq geschriebenen Brainfuck-Interpreters - Die Benchmark-Daten wurden auf einem Linux-System mit AMD Ryzen 5 5500U erzeugt
- Verwendeter Befehl:
bench.sh target/release/jaq jq-1.8.1 gojq-0.12.17 | tee bench.json - In der Tabelle werden die Ergebnisse von
jaq-3.0,jq-1.8.1undgojq-0.12.18in Millisekunden angezeigt N/Abedeutet einen Fehler oder mehr als 10 Sekunden Laufzeit
- Verwendeter Befehl:
- Zusammenfassung der Ergebnisse
jaq-3.0war in 20 Benchmarks am schnellstenjq-1.8.1war in 5 Benchmarks am schnellstengojq-0.12.18war in 6 Benchmarks am schnellsten
gojqist beitree-flattendeutlich schneller, weil derflatten-Filter nativ statt als Definition implementiert ist
Sicherheitsmodell und Einschränkungen
- jaq versucht Folgendes zu gewährleisten
- Keine Panics, außer in Fällen von Ressourcenerschöpfung
- Speichersicherheit, also keine Beschädigung des Speichers
- Abgesehen vom Lesen von Dateien vor der Ausführung von jq-Filtern können Eingabedaten und jq-Filter keine I/O-Operationen starten
- Wenn diese Garantien verletzt werden, gilt das als Bug und sollte gemeldet werden
- jaq adressiert keinerlei Form von Ressourcenerschöpfung
- Die Ausführungszeit kann unbegrenzt lang werden
- Es kann unbegrenzt Speicher verbrauchen
- Es kann unbegrenzt Stack-Speicher verbrauchen
- Beispielsweise kann es beim Einlesen von Eingabedaten oder beim Ausführen von jq-Filtern zu Stack-Overflow kommen
jaq -nr 'repeat("[")' | jaqjaq -n 'def f: 1+f; f'
Audits und Tests
- jaq core wurde im Rahmen von zwei NLnet-Grants von Radically Open Security auditiert
- Beim ersten und zweiten Sicherheitsaudit wurden Probleme mit mittlerem oder niedrigem Schweregrad gefunden
- Alle Punkte aus den Sicherheitsaudits wurden bearbeitet, und unter
jaq-core/fuzzwurden mehrere Fuzzing-Targets für jaq hinzugefügt - Der JSON-Parser hifijson von jaq hatte bereits Fuzzing-Targets
- jaq verfügt über eine Testsuite mit mehr als 500 Tests
Anwendungsfälle von Nutzern
- Ein Nutzer bewertete
jaqals deutlich hilfreicher, alsjq-Support selbst direkt zu implementieren, und hob hervor, dass sich dank der Erweiterbarkeit über dasValT-Trait jq-Unterstützung leicht zu eigenen Typen hinzufügen ließ - Ein anderer Nutzer berichtete, dass ein Rust-Programm mit jaq dreimal alle Abfragen auf einer gesamten Datei ausführen konnte, während das
jq-PyPI-Paket für Python zusammen mit einer Python-Schleife nur eine einzige Abfrage über die gesamte Datei schaffte - Im Fall des
wsjq-Interpreters wurde jaq als deutlich schneller als andere jq-Implementierungen beschrieben, und der Fokus auf Genauigkeit wurde positiv hervorgehoben- In diesem
wsjq-Benchmark war jaq 5- bis 10-mal schneller als jq und 15- bis 196-mal schneller als gojq
- In diesem
- Ein Nutzer, der Certificate-Transparency-Log-Daten mit
certstream-serververarbeitete, hatte Probleme mit einerjq-Pipeline; nach dem Wechsel zu jaq konnte er dank der schnelleren Startzeit selbst auf einer schwach ausgestatteten VM mithalten
Finanzierung
- Das jaq-Projekt wird über die von NLnet eingerichteten Fonds NGI0 Entrust und NGI0 Commons unterstützt
- Die Finanzierung erfolgt im Rahmen des Programms Next Generation Internet der European Commission
- Zusätzliche Mittel kommen vom Swiss State Secretariat for Education, Research and Innovation
1 Kommentare
Hacker-News-Kommentare
Wenn man bedenkt, dass die Entwicklung von jq fünf Jahre lang stillstand und erst kürzlich wiederbelebt wurde, ist es nicht verwunderlich, dass sich in der Zwischenzeit Meldungen zu bekannten wie neuen Bugs angesammelt haben.
Jetzt dürfte das Projekt wieder Fahrt aufnehmen und die lange aufgelaufene Liste offener Punkte nach und nach abarbeiten.
Mir gefällt die Art, im README ähnliche oder inspirierte Projekte mit vorzustellen, statt sie als Ersatz darzustellen.
Durch das README dieses Projekts bin ich auf https://github.com/yamafaktory/jql gestoßen; das war ein Tool, nach dem ich schon lange gesucht hatte, danke dafür.
Ohne JAQ schlechtmachen zu wollen: Die jq-artige Syntax ist einfach zu schwer zu verstehen, daher passt jql besser zu mir.
Es flacht JSON in Zeilen im Key-Value-Format ab, sodass es gut zu einfachen Stream-Operationen wie
greppasst: https://github.com/tomnomnom/gronAllerdings hatte ich eine wirklich SQL-ähnliche Erfahrung erwartet. Ich verstehe nicht, warum man nicht einfach SQL kopiert und Abfragen wie
"SELECT * FROM $json WHERE x>1"ermöglicht.Es wirkt, als wollten alle ihre eigene obskure, symbolische Abfragesprache bauen, als wäre es Code Golf. Ich wünschte, man käme weg von extrem knapper, aber nicht selbsterklärender alter Unix-Syntax und näher an den PowerShell-Ansatz heran.
|={"b""d"=2, "c"}scheint etwa dasselbe zu bedeuten wie jqsselect(."b"."d" == 2 or ."c" != null), und die jq-Variante fühlt sich trotz größerer Länge klarer an.In der Praxis bräuchte man wohl
.[] | select(...), aber bei jql kann es ähnliche Annahmen geben; da ich nicht sicher bin, ob das Beispiel vollständig ist, ändert das am Fazit nicht viel.Es scheint auch möglich zu sein, es auf sich selbst anzuwenden oder „Makros“ zu verwenden.
Ich mag die Idee von jq, aber weil ich es nicht häufig benutze, muss ich jedes Mal die Syntax im Handbuch nachschlagen, wenn ich etwas Bestimmtes erreichen will.
Leider bestehen 99 % dessen, was ich mit jq mache, aus
| jq ..Unabhängig davon begann ich, eine Konfigurationssprache zu bauen, und stellte fest, dass sie sich auch für JSON-Abfragen ziemlich gut eignete: https://docs.ruuda.nl/rcl/rcl_query/
Hier ist ein Beispiel, das ich mit jq nicht lösen konnte, mit RCL aber schon: https://fosstodon.org/@ruuda/111120049523534027
Wenn man die gewünschte Aufgabe zusammen mit einem gekürzten Beispiel des ursprünglichen JSON liefert, erzeugt es ein korrektes jq-Skript.
Bei komplexen Anforderungen ist es einfacher und zuverlässiger, sich mit Copilot schrittweise zu einer Lösung vorzuarbeiten, statt alles auf einmal exakt zu beschreiben. Während der Iteration kommen einem manchmal sogar bessere Ideen als am Anfang.
ChatGPT oder andere Tools dürften ähnlich funktionieren.
https://github.com/01mf02/jaq/blob/main/Cargo.lock
Es gibt ziemlich viele Abhängigkeiten.
Bei vielen Abhängigkeiten scheint mit der Zeit das Risiko zu wachsen, dass sie grundsätzlich inkompatibel werden, und die Wartung dürfte zu einer großen Aufgabe werden.
Ich frage mich zum Beispiel, ob es auch in zwei Jahren noch problemlos kompiliert.
jq ist ein sehr mächtiges Tool, aber heutzutage wird auch DuckDB viel genutzt.
Wenn die Daten einigermaßen tabellarisch sind, ist SQL die deutlich natürlichere Sprache.
Es ist ein wenig ähnlich wie LINQ in C#, aber SQL ist stärker standardisiert, deshalb gefällt es mir besser.
Es wäre großartig, wenn man native Collections innerhalb einer Sprache mit SQL abfragen könnte; noch besser wäre es, wenn sich Collections transparent in Sqlite speichern ließen.
Wenn ich Code sehe, der nach dem Abrufen von Daten aus einer Datenbank o. Ä. nur einfache Verarbeitung per Schleife oder Stream-API macht, finde ich das immer schade. Für solche Zwecke ist SQL viel höher abstrahiert und knapper als Java/Kotlin/Python/JavaScript.
Ich speichere die komplette JSON-Rohausgabe in sqlite-Tabellen, erstelle dort virtuelle Spalten und lasse dann das Ergebnis von
selectdurch eine Shell-Schleife laufen.Dadurch lösen sich verschachtelte Schleifen auf, und die Debugbarkeit wird viel besser, weil man die exakten Datensätze in der DB prüfen und erneut ausführen kann.
Mir wurde klar, dass das, was ich baue, ein DAG ist, und ich starte immer ab dem zuletzt erfolgreich verarbeiteten Datensatz neu. Ich frage mich, ob es ein
Make-ähnliches Tool gibt, um das auszudrücken.Make hat keine SQL-Targets, und vollwertige DAG-Prozessoren wie Airflow sind zu schwergewichtig, um Shell-Schnipsel zusammenzukleben.
Trotzdem ist es weiterhin schwer, rekursive Abfragen in SQL knapp auszudrücken.
https://github.com/dinedal/textql
Aus Sicht der Korrektheit frage ich mich, ob uint64-Zahlen ohne Abschneiden angezeigt werden können.
Das ist derzeit der nervigste Punkt an jq.
Allerdings stellt die neuere Spezifikation RFC 8259 klar, dass sie nur die Textform von Zahlen festlegt und keine Semantik definiert.
In der Praxis behandeln die meisten Implementierungen JSON aber als Teilmenge von JavaScript, was zu der Annahme führt, dass Zahlen 64-Bit-Gleitkommazahlen sind.
Dort heißt es, dass Dezimalzahl-Literale zur Erhaltung der Präzision verwendet werden und Vergleichsoperatoren die Präzision respektieren, während arithmetische Operationen abschneiden können.
Derzeit wird auf decimal64 abgeschnitten, was etwas verwirrend ist; im nächsten Release soll das entsprechend dem Vorschlag der JSON-Spezifikation auf binary64(double) geändert werden: https://github.com/jqlang/jq/pull/2949
Seit ich zu jless gewechselt bin, habe ich nicht mehr zurückgeschaut.
Die Benutzeroberfläche ist den anderen weit voraus.
jq ist kein einfacher Viewer, sondern ein Prozessor für eine JSON-Abfragesprache.
Es ist niedlich, dass irgendwo in Rust eine Terminal-Line-Art-Bibliothek steckt, aber als ich jaq ausprobierte, kippte es Megabytes an Escape-Codes in iTerm, bis iTerm schließlich versuchte, es an den Drucker auszugeben.
Das war zu clever.
In einem Kontext wie
echo *json | rush -- jaq -rf ./this-program.jq {} | datamash ...halte ich es nicht für passend, dem TTY künstlerische Effekte zu verpassen.Die Ursache des Fehlers war jedenfalls, dass
jaqkeinstrftimehat.Mein erster Eindruck ist, dass es zwar schöne Fehlermeldungen gibt, aber kein
halt_error/0.Nachdem ich
halt_errorauskommentiert hatte, war es langsamer als jq und gojq.Mit derselben Eingabe brauchte
jqetwa 0,023 Sekunden,gojqetwa 0,070 Sekunden undjaqetwa 0,103 Sekunden.Das verwendete
aoc22-13.jqist https://pastebin.com/raw/YiUjEu2n, undinput.txtist https://pastebin.com/raw/X0FSyTNf.Ich habe angefangen, statt jq yq zu verwenden, und frage mich, ob es wichtige Unterschiede gibt.
Persönlich bevorzuge ich https://github.com/mikefarah/yq gegenüber https://github.com/kislyuk/yq.
Ich verstehe, dass YAML-Verarbeitung viel schwieriger ist als JSON, aber obwohl yq beim Wechsel von Version 3 auf 4 die Syntax näher an jq gebracht hat, ist sie irgendwie immer noch nicht ganz gleich.
Außerdem hat yq kein if-then-else, was wie schlechtes Design oder ein Versäumnis wirkt: https://github.com/mikefarah/yq/issues/95
Wenn man YAML verarbeiten muss, funktioniert yq gut und kommt auch mit Kommentaren ziemlich gut zurecht, aber für reine JSON-Verarbeitung ist jq das bessere Tool.