jq
Guy Lapalme, RALI-DIRO, Université de Montréal, juin 2024
[English version of this document]
jq
est un outil en ligne de commande qui permet de manipuler des fichiers .json
ou .jsonl
pour les valider, les formater, en extraire des informations ou les transformer. On peut le considérer comme l'équivalent de sed
ou awk
pour les fichiers de texte dont les informations des lignes sont séparées par des tabulations. Le système est rapide et efficace pour traiter de gros fichiers, y compris en mode streaming.
La documentation officielle contient un tutoriel présentant quelques exemples ainsi qu’un manuel qui énumère toutes les fonctions illustrées par un court exemple. Elle ne décrit cependant pas comment enchaîner ces fonctions pour effectuer des tâches intéressantes. La syntaxe des expressions jq
peut sembler déroutante, car elle s’appuie sur la composition de filtres plutôt que sur une notation fonctionnelle plus familière. jq
est un véritable langage de programmation doté de structures de contrôle, de variables, de fonctions et même de modules. Comme toutes mes applications se sont limitées à la composition d'une seule expression, ce document se concentrera sur cet aspect du langage. Ces expressions permettent tout de même d'effectuer des tâches utiles.
Il existe plusieurs tutoriels sur le web, dont celui-ci qui est excellent, mais maintenant truffé de publicités. Cependant, ils n'insistent pas sur les éléments que je trouve les plus utiles dans les cas d'utilisation que j'ai rencontrés, c’est-à-dire l'extraction et la transformation de données JSON dans des fichiers.
Ce document me servira donc d'aide-mémoire même si, après l'avoir rédigé, je suis moins susceptible d'en avoir besoin ; toutefois, j'espère qu'il sera utile à d'autres personnes.
Une expression jq
est un filtre qui transforme une entrée en une sortie. Tous les éléments du langage jq
sont des filtres, même un nombre ou une chaîne qui peuvent être considérés comme des filtres qui ignorent leur entrée et produisent toujours la même valeur.
Une fois installé, jq
est appelé de la ligne commande:
jq
[options] '
filtre'
fichier, le filtre étant entouré d'apostrophes simples pour éviter qu'il soit interprété par le shell avant d'être passé à jq
.
Un filtre est appliqué sur un flot de données JSON. jq
analyse ce flot comme une séquence de valeurs JSON séparées par des espaces qui sont validées et données en entrée au filtre une à une. La ou les sorties du filtre sont envoyées sur stdout comme une séquence de valeurs JSON séparées par un newline. Par défaut, la valeur JSON est affichée avec une indentation pour faire ressortir la structure du JSON (pretty-print). Cette indentation n'est pas appliquée si l'option -c
est spécifiée.
Rappelons qu'outre les valeurs primitives (nombre, chaîne de caractères, booléen, null
), JSON fournit deux structures de données:
objet : couples clé:
valeur séparées par des virgules à l'intérieur d'accolades.
tableau : valeurs séparées par des virgules entourées de crochets.
Voici une sélection de fonctions parmi la centaine décrites dans le manuel.
.
: identité: retourne l'entrée telle quelle: ce filtre pourrait sembler inutile, mais si on l'applique sur un fichier JSON, il le valide et en sort une version indentée.
f1 | f2
: composition de filtres: redirige la sortie du filtre f1 vers l'entrée de f2.
.["clé"]
: indice d'objet : appliqué à un objet, retourne la valeur associée à clé si elle figure dans l'objet, sinon null
. Si clé est un identificateur simple, on peut simplifier la notation en .clé
.
.[nombre]
: indice de tableau : appliqué à un tableau, retourne la valeur à la position indiquée par le nombre, nombre peut être négatif pour indicer à partir de la fin.
.[]
: génération de valeurs : appliqué à un objet, retourne toutes les valeurs de l'objet comme résultats séparées; .clé | .[]
peut s'écrire aussi .clé[]
.
f1 , f2
: concaténation de filtres: la sortie de f2 est ajoutée à la fin de la sortie de f1.
{…}
: construction d'un objet : réunit les éléments du contenu qui doivent être de la forme clé:
valeur en un seul objet. clé est souvent une chaîne de caractère, mais peut aussi être une expression (p.ex. un filtre) si on la met entre parenthèses.
[…]
: construction de tableau : réunit le contenu en un tableau
(…)
: groupement : groupe une sous-expression selon les conventions habituelles
select(f)
: sélection : ne garder de l'entrée que les éléments pour lesquels f
retourne true
keys
: clés d'un objet : retourne un tableau avec les clés de l'objet en entrée. Le tableau des valeurs d'un objet est obtenu avec .[]
(voir ci-haut).
to_entries
: couples clé-valeur d'un objet : transforme un objet {cléi:valeuri}
en un tableau d'objets de la forme {"key":cléi:"value":valeuri}
del(clé)
: suppression: enlever la clé et la valeur associée dans un objet
f1 + f2
: addition de filtres: dépend des types des filtres: nombres: addition arithmétique; chaîne ou tableau: concaténation; objets: fusion.
f1 - f2
: soustraction de filtres: dépend des types des filtres: nombres: soustraction arithmétique; tableau: enlève de f1 les éléments de f2.
==
!=
>
>=
<
<=
: comparaison selon les conventions habituelles
and
or
not
: combinaison de booléens
test(regex)
: vérification avec une expression régulière
startswith(str)
endswith(str)
: vérification : est-ce que le début ou la fin de chaîne correspond à str.
split(str)
join(str)
: conversion chaine <=>tableau : tableau des sous-chaînes entre les str, créer une chaîne à partir d'un tableau en ajoutant str entre chaque élément.
Conseil: essayez ces exemples avec jqTerm ou jqplay qui permettent de les faire exécuter depuis une page web et d'afficher les résultats immédiatement.
.json
La structure JSON suivante représente un extrait d'un dictionnaire anglais dont les clés sont des mots. À chaque mot est associé un objet qui décrit des informations selon sa catégorie (A
: adjectif, N
: nom,. V
: verbe...). À chaque catégorie est associée une table de flexions (tab
) et, pour certains, si le mot est courant (basic
). Un nom peut être comptable (yes
), non-comptable (no
) ou les deux (both
) .
291{"comfortable":{"A":{"tab":"a1"},
2 "basic":true},
3 "comic":{"A":{"tab":"a1"},
4 "N":{"tab":"n1",
5 "cnt":"yes"}},
6 "comical":{"A":{"tab":"a1"}},
7 "comically":{"Adv":{"tab":"b1"}},
8 "coming":{"A":{"tab":"a1"},
9 "N":{"tab":"n1",
10 "cnt":"yes"},
11 "basic":true},
12 "comity":{"N":{"tab":"n5",
13 "cnt":"no"}},
14 "comma":{"N":{"tab":"n1",
15 "cnt":"yes"}},
16 "command":{"N":{"tab":"n1",
17 "cnt":"both"},
18 "V":{"tab":"v1"},
19 "basic":true},
20 "commandant":{"N":{"tab":"n1",
21 "cnt":"yes"}},
22 "commandment":{"N":{"tab":"n1",
23 "cnt":"yes"}},
24 "commando":{"N":{"tab":"n2",
25 "cnt":"yes"}},
26 "commemorate":{"V":{"tab":"v3"}},
27 "commemoration":{"N":{"tab":"n1",
28 "cnt":"both"}},
29 "commemorative":{"A":{"tab":"a1"}}}
keys
: tableau de tous les mots ⇒ ["comfortable", "comic","comical", ...]
to_entries[]|select(.value.basic)|.key
: liste des mots avec l'indicateur basic
: select devant s'appliquer sur un tableau, il faut d'abord transformer l'entrée en tableau et ensuite ne choisir que la clé. Résultat sur plusieurs lignes ⇒
31"comfortable"
2"coming"
3"command"
to_entries|.[]|{(.key):.value|del(.basic)}
: liste de structures JSON dont la clé est le mot, mais où on a enlevé le champ basic
. ⇒
191{
2 "comfortable": {
3 "A": {
4 "tab": "a1"
5 }
6 }
7}
8{
9 "comic": {
10 "A": {
11 "tab": "a1"
12 },
13 "N": {
14 "tab": "n1",
15 "cnt": "yes"
16 }
17 }
18}
19 .
.jsonl
Chaque ligne d'un fichier JSON Lines est un objet json décrivant un mot. Ce fichier aurait pu être créé à partir du précédent avec l'expression to_entries[] | {word:.key}+.value
141{"word":"comfortable","A":{"tab":"a1"},"basic":true}
2{"word":"comic","A":{"tab":"a1"},"N":{"tab":"n1","cnt":"yes"}}
3{"word":"comical","A":{"tab":"a1"}}
4{"word":"comically","Adv":{"tab":"b1"}}
5{"word":"coming","A":{"tab":"a1"},"N":{"tab":"n1","cnt":"yes"},"basic":true}
6{"word":"comity","N":{"tab":"n5","cnt":"no"}}
7{"word":"comma","N":{"tab":"n1","cnt":"yes"}}
8{"word":"command","N":{"tab":"n1","cnt":"both"},"V":{"tab":"v1"},"basic":true}
9{"word":"commandant","N":{"tab":"n1","cnt":"yes"}}
10{"word":"commandment","N":{"tab":"n1","cnt":"yes"}}
11{"word":"commando","N":{"tab":"n2","cnt":"yes"}}
12{"word":"commemorate","V":{"tab":"v3"}}
13{"word":"commemoration","N":{"tab":"n1","cnt":"both"}}
14{"word":"commemorative","A":{"tab":"a1"}}
.word
: liste de tous les mots ⇒ ["comfortable", "comic","comical", ...]
select(.basic)|.word
: liste des mots avec l'indicateur basic
⇒
31"comfortable"
2"coming"
3"command"
{(.word):.|del(.basic)}
: liste de structures JSON dont la clé est le mot, mais où on a enlevé le champ basic
. ⇒
211{
2 "comfortable": {
3 "word": "comfortable",
4 "A": {
5 "tab": "a1"
6 }
7 }
8}
9{
10 "comic": {
11 "word": "comic",
12 "A": {
13 "tab": "a1"
14 },
15 "N": {
16 "tab": "n1",
17 "cnt": "yes"
18 }
19 }
20}
21
Il est possible de traiter les lignes d'un fichier .jsonl
comme un tableau de structures en spécifiant l'option --slurp
(-s
).
Pour les curieux: avec l'option -s
, l'expression
11 reduce .[] as $elem ({}; .+ {($elem|.word) : $elem|del(.word) })
recrée la structure JSON de la section précédente en partant d'un objet vide; pour chaque élément du tableau, on fusionne un objet ayant pour clé la valeur de la clé word
et, pour valeur, toutes les propriétés de l'élément sauf le champ word
.
Exploring jq : Conseils de haut niveau pour l'utilisation de jq
en conjonction avec d'autres systèmes.
jq Language Description: définition plus formelle du langage jq
avec plusieurs explications intéressantes.
Exemples de programmes jq
plus complexes:
Tests en ligne