- jaq est un clone de l’outil de traitement de données JSON
jq, qui vise une implémentation plus précise et prévisible tout en restant compatible avecjqdans la plupart des cas - Le programme en ligne de commande
jaqpeut être utilisé comme remplaçant transparent dejq, et la bibliothèque Rustjaq-corepermet de compiler et d’exécuter des programmes jq dans des programmes Rust - Il prend en charge YAML, CBOR, TOML et XML, absents de
jq, tandis quejaq-corepeut être utilisé en toute sécurité dans des environnements multithread et prend en charge des types de données arbitraires au-delà du JSON - Dans l’évaluation des performances,
jaq-3.0a été le plus rapide sur 20 des 31 benchmarks, contre 5 pourjq-1.8.1et 6 pourgojq-0.12.18 - Côté sécurité, il cherche à garantir l’absence de panic, la sûreté mémoire et des limites d’I/O pour les données d’entrée et les filtres jq, mais ne traite pas les cas d’épuisement des ressources comme le temps, la mémoire ou la pile
Ce que propose jaq
- jaq est un clone de l’outil de traitement de données JSON
jq, prononcé/ʒaːk/comme Jacques - Il prend en charge des formats de données absents de
jq-
YAML
-
CBOR
-
TOML
- XML
- Une documentation distincte est disponible, et il peut être testé dans le playground
- jaq est proposé sous deux formes
- Programme en ligne de commande
jaq: utilisable comme remplaçant transparent dejq - Bibliothèque
jaq-core: permet de compiler et d’exécuter des programmes jq dans des programmes Rust
-
Objectifs de conception
-
Précision
- jaq vise une implémentation de jq plus correcte et plus prévisible, tout en restant compatible avec
jqdans la plupart des cas
- jaq vise une implémentation de jq plus correcte et plus prévisible, tout en restant compatible avec
-
Performances
- jaq a été créé à l’origine parce que le long temps de démarrage de
jq 1.6était gênant, avec un démarrage d’environ 50 ms dans cet environnement - Ce temps de démarrage est particulièrement visible lors du traitement d’un grand nombre de petits fichiers
- Le temps de démarrage a été fortement amélioré dans
jq 1.7, mais jaq reste plus rapide quejqsur plusieurs benchmarks
- jaq a été créé à l’origine parce que le long temps de démarrage de
-
Simplicité
- jaq recherche une implémentation simple et compacte afin de réduire les risques de bugs et de faciliter les contributions
Installation et compilation
- Des binaires pour Linux, Mac et Windows sont disponibles sur la page des releases
- Sur macOS ou Linux, il peut être installé via homebrew
brew install jaqbrew install --HEAD jaq
- Pour compiler depuis les sources, un toolchain Rust est nécessaire
cargo install --locked jaqcargo install --locked --git https://github.com/01mf02/jaq
- Si le dépôt a été cloné, il est possible de compiler ou d’installer avec
cargo build --releaseoucargo install --locked --path jaq - jaq est censé fonctionner sur tous les systèmes pris en charge par Rust ; dans le cas contraire, il est demandé d’ouvrir une issue
Évaluation des performances
- L’évaluation des performances se compose de plusieurs benchmarks comparant jaq, jq et gojq
- Le benchmark
emptymesure le temps de démarrage en exécutantnfois le filtreemptyavec une entrée null - Le benchmark
bf-fibexécute un script Brainfuck qui génère des nombres de Fibonacci à l’aide d’un interpréteur Brainfuck écrit en jq - Les données de benchmark ont été générées sur un système Linux équipé d’un AMD Ryzen 5 5500U
- La commande utilisée est
bench.sh target/release/jaq jq-1.8.1 gojq-0.12.17 | tee bench.json - Le tableau affiche les résultats de
jaq-3.0,jq-1.8.1etgojq-0.12.18en millisecondes N/Asignifie une erreur ou un dépassement de 10 secondes
- La commande utilisée est
- Résumé des résultats
jaq-3.0est le plus rapide sur 20 benchmarksjq-1.8.1est le plus rapide sur 5 benchmarksgojq-0.12.18est le plus rapide sur 6 benchmarks
gojqest bien plus rapide surtree-flatten, car le filtreflatteny est implémenté nativement plutôt que défini
Modèle de sécurité et limites
- jaq cherche à garantir les points suivants
- Aucun panic en dehors des cas d’épuisement des ressources
- Une sûreté mémoire sans corruption de la mémoire
- À l’exception de la lecture de fichiers avant l’exécution du filtre jq, les données d’entrée et le filtre jq ne peuvent pas initier d’opérations d’I/O
- Toute violation de ces garanties est considérée comme un bug et doit être signalée
- jaq ne traite aucun type d’épuisement des ressources
- Le temps d’exécution peut devenir illimité
- La mémoire peut être consommée sans limite
- L’espace de pile peut être consommé sans limite
- Par exemple, un dépassement de pile peut se produire lors de la lecture des données d’entrée ou de l’exécution d’un filtre jq
jaq -nr 'repeat("[")' | jaqjaq -n 'def f: 1+f; f'
Audits et tests
- jaq core a fait l’objet d’audits par Radically Open Security dans le cadre de deux subventions NLnet
- Les premier et deuxième audits de sécurité ont mis au jour des problèmes de gravité moyenne ou faible
- Tous les problèmes relevés lors des audits ont été traités, et plusieurs cibles de fuzzing pour jaq ont été ajoutées dans
jaq-core/fuzz - Le parseur JSON de jaq, hifijson, disposait déjà lui aussi de cibles de fuzzing
- jaq dispose d’une suite de tests de plus de 500 tests
Cas d’usage
- Un utilisateur a estimé que
jaql’avait beaucoup aidé par rapport à l’implémentation directe de la prise en charge dejq, et que l’extensibilité via le traitValTlui avait permis d’ajouter facilement la prise en charge de jq à ses propres types - Un autre utilisateur a indiqué que son programme Rust utilisant jaq pouvait exécuter trois fois toutes les requêtes sur l’ensemble d’un fichier pendant que le crate PyPI
jqde Python et une boucle Python n’exécutaient qu’une seule requête sur ce même fichier - Dans le cas de l’interpréteur
wsjq, jaq a été jugé nettement plus rapide que les autres implémentations de jq, avec un accent impressionnant sur la précision- Sur ce benchmark
wsjq, jaq est 5 à 10 fois plus rapide que jq et 15 à 196 fois plus rapide que gojq
- Sur ce benchmark
- Un utilisateur qui traitait des données de certificate transparency logs avec
certstream-serverrencontrait des problèmes avec un pipelinejq, et a indiqué qu’après être passé à jaq, son temps de démarrage plus rapide lui permettait de suivre le rythme même sur une VM peu puissante
Financement
- Le projet jaq est soutenu via les fonds NGI0 Entrust et NGI0 Commons, créés par NLnet
- Le financement est fourni dans le cadre du programme Next Generation Internet de la Commission européenne
- Un financement complémentaire est fourni par le Secrétariat d’État suisse à la formation, à la recherche et à l’innovation
1 commentaires
Commentaires Hacker News
Étant donné que le développement de jq est resté à l’arrêt pendant 5 ans avant de reprendre tout récemment, il n’est pas surprenant que des signalements se soient accumulés pendant ce temps, qu’il s’agisse de bugs connus ou de nouveaux bugs
On dirait qu’ils vont maintenant retrouver de la vitesse et commencer à résorber progressivement la liste des problèmes non résolus accumulés de longue date
J’aime bien l’idée de présenter aussi dans le README des projets similaires ou inspirés, sans forcément les présenter comme des remplaçants
C’est grâce au README de ce projet que j’ai découvert https://github.com/yamafaktory/jql, et comme c’est un outil que je cherchais depuis longtemps, j’en suis reconnaissant
Ce n’est pas pour rabaisser JAQ, mais la syntaxe à la JQ est beaucoup trop difficile à comprendre, donc jql me convient mieux
Il aplatit le JSON en lignes clé-valeur, ce qui fonctionne bien avec des traitements de flux simples comme
grep: https://github.com/tomnomnom/gronCela dit, je m’attendais à une vraie expérience proche de SQL. Je ne comprends pas pourquoi ils ne reprennent pas simplement SQL pour permettre des requêtes du type
"SELECT * FROM $json WHERE x>1"On dirait que tout le monde veut créer son propre langage de requête symbolique obscur, comme s’il faisait du code golf. J’aimerais qu’on s’éloigne des anciennes syntaxes Unix, extrêmement courtes mais peu évidentes, pour aller vers quelque chose de plus proche de l’approche PowerShell
|={"b""d"=2, "c"}semble vouloir dire la même chose queselect(."b"."d" == 2 or ."c" != null)en jq, mais même si la version jq est plus longue, elle me paraît plus claireEn pratique, il faudrait sûrement
.[] | select(...), mais jql repose peut-être sur une hypothèse similaire, et comme je ne suis pas sûr que l’exemple soit complet, cela ne change pas vraiment la conclusionOn dirait qu’on peut aussi l’appliquer à lui-même ou utiliser des sortes de « macros »
J’aime l’idée de jq, mais comme je ne l’utilise pas souvent, je dois retourner voir la syntaxe dans le manuel à chaque fois que je veux faire quelque chose
Malheureusement, 99 % de ce que je fais avec jq, c’est
| jq .À part ça, j’ai commencé à créer un langage de configuration, et il s’est avéré qu’il était aussi assez bon pour interroger du JSON : https://docs.ruuda.nl/rcl/rcl_query/
Voici un exemple que je n’ai pas pu résoudre avec jq mais que j’ai pu traiter avec RCL : https://fosstodon.org/@ruuda/111120049523534027
Si on lui donne la tâche voulue avec un échantillon réduit du JSON source, il produit le bon script jq
Pour les besoins complexes, il est plus simple et plus fiable de guider Copilot vers une solution par itérations successives que d’essayer de tout formuler parfaitement d’un seul coup. Pendant ces itérations, on finit aussi parfois par avoir de meilleures idées qu’au départ
ChatGPT ou d’autres outils devraient fonctionner de façon similaire
https://github.com/01mf02/jaq/blob/main/Cargo.lock
Il y a pas mal de dépendances
Avec beaucoup de dépendances, le risque qu’elles deviennent fondamentalement incompatibles entre elles au fil du temps semble plus élevé, et la maintenance paraît être un gros travail
Par exemple, est-ce que ça compilera encore correctement dans 2 ans ?
jqest un outil très puissant, mais de nos jours DuckDB est aussi beaucoup utiliséSi les données ont plus ou moins une forme tabulaire, SQL est un langage bien plus naturel
C’est un peu similaire à LINQ en C#, mais j’aime davantage SQL parce qu’il est plus standardisé
Ce serait formidable de pouvoir interroger des collections natives en SQL dans un langage, et mieux encore si ces collections pouvaient être stockées de façon transparente dans Sqlite
Quand je vois du code qui récupère des données depuis une base ou autre, puis fait un traitement simple avec des boucles ou une API de streams, je trouve toujours ça dommage. Pour ce genre d’usage, SQL est bien plus haut niveau et concis que Java/Kotlin/Python/JavaScript
Je stocke toute la sortie JSON brute dans des tables sqlite, puis j’y crée des colonnes virtuelles avant de faire tourner le résultat de
selectdans une boucle shellÇa supprime les boucles imbriquées, et le débogage est bien meilleur parce qu’on peut inspecter dans la base les enregistrements exacts et relancer
J’ai réalisé que ce que je construisais était un DAG, et je redémarre toujours à partir du dernier enregistrement traité avec succès. Je me demande s’il existe un outil semblable à
Makepour exprimer celaMake n’a pas de cible SQL, et un orchestrateur DAG complet comme Airflow est trop lourd pour assembler des fragments shell
Cela dit, il reste difficile d’exprimer des requêtes récursives de manière concise en SQL
https://github.com/dinedal/textql
Du point de vue de l’exactitude, je me demande si les nombres uint64 peuvent être affichés sans être tronqués
C’est actuellement l’aspect le plus agaçant de jq pour moi
Cela dit, la spécification la plus récente, RFC 8259, corrige cela en précisant qu’elle ne définit que le format textuel des nombres, pas leur sémantique
En pratique, la plupart des implémentations traitent JSON comme un sous-ensemble de JavaScript, ce qui conduit à supposer que les nombres sont des flottants 64 bits
Il est indiqué que les littéraux numériques décimaux préservent la précision, que les comparaisons la respectent aussi, mais que les opérations arithmétiques peuvent entraîner une troncature
Actuellement, c’est tronqué en decimal64, ce qui est un peu déroutant, mais cela devrait être corrigé dans la prochaine version pour revenir à une troncature en binary64(double), conformément à la proposition de la spécification JSON : https://github.com/jqlang/jq/pull/2949
Depuis que je suis passé à jless, je ne reviendrais pas en arrière
L’interface utilisateur est très au-dessus des autres
jq n’est pas un simple visualiseur, c’est un processeur de langage de requête JSON
C’est mignon qu’il existe quelque part en Rust une bibliothèque de line art pour terminal, mais quand j’ai essayé d’exécuter jaq, il a déversé des mégaoctets de codes d’échappement dans iTerm, qui a fini par essayer de les envoyer à l’impression
C’était trop malin pour son propre bien
Dans un contexte comme
echo *json | rush -- jaq -rf ./this-program.jq {} | datamash ..., je ne pense pas qu’il soit approprié d’essayer d’ajouter des effets artistiques sur un TTYQuoi qu’il en soit, la cause de l’erreur était que
jaqn’a passtrftimeMa première impression est qu’il y a de jolis messages d’erreur, mais pas de
halt_error/0Après avoir commenté
halt_error, c’était plus lent que jq et gojqSur la même entrée,
jqprenait environ 0,023 s,gojqenviron 0,070 s, etjaqenviron 0,103 sLe
aoc22-13.jqutilisé est https://pastebin.com/raw/YiUjEu2n etinput.txtest https://pastebin.com/raw/X0FSyTNfJ’ai commencé à utiliser yq à la place de jq, et je me demande s’il y a des différences importantes
Personnellement, je préfère https://github.com/mikefarah/yq à https://github.com/kislyuk/yq
Je comprends que traiter du YAML est bien plus difficile que du JSON, mais même si yq a rapproché sa syntaxe de celle de jq entre les versions 3 et 4, cela ne semble toujours pas tout à fait identique
Et puis yq n’a pas de if-then-else, ce qui donne l’impression d’un mauvais choix de conception ou d’un oubli : https://github.com/mikefarah/yq/issues/95
Quand il faut traiter du YAML, yq fonctionne bien et gère assez bien les commentaires, mais pour du JSON pur, jq reste le meilleur outil