jq fonctionne en faisant passer les données JSON entrantes à travers une expression unique (écrite sous la forme d'un pipeline de filtres) pour obtenir les données transformées souhaitées.
jq
Le langage jq est implémenté par le programme jq.
Ce programme propose plusieurs options de ligne de commande bien pratiques pour contrôler la façon dont l'entrée est consommée et dont la sortie est présentée.
Dans les exemples ci-dessous, tu rencontreras :
-n ou --null-input
Normalement, on donne au programme jq un fichier à lire, ou bien on envoie des données sur son entrée.
L'option --null-input permet de générer des données JSON sans aucune entrée.
-c ou --compact-output
Par défaut, jq affiche sa sortie de façon lisible (pretty-print).
C'est extrêmement utile à un humain de consulter les données quand elles sont bien formatées.
En revanche, ce n'est pas nécessaire pour une machine : l'option --compact-output supprime les espaces de mise en forme pour réduire la taille du JSON produit.
-f filename ou --from-file filename
Lit le programme jq depuis filename au lieu de le fournir sur la ligne de commande.
sed et awk utilisent tous les deux l'option -f dans le même but.
Tu verras cet usage dans les scripts de test des exercices d'entraînement.
Consulte le manuel pour plus de détails sur toutes les options.
Les filtres sont aussi appelés expressions.
Un filtre prend une entrée et produit une sortie.
Comme lorsqu'on travaille dans un shell Unix, tu peux relier des filtres avec un pipe | pour connecter la sortie d'un filtre à l'entrée d'un autre.
.
C'est le filtre le plus simple.
Il se contente de transmettre son entrée à sa sortie.
Par exemple, comme jq met en forme de façon lisible par défaut, passer du JSON au filtre . donne une sortie bien formatée, sans effort !
$ echo '[1, 2, 3]' | jq '.'
[
1,
2,
3
]
Voici une introduction rapide à la manipulation des tableaux. Nous aborderons ce sujet plus en détail par la suite.
Les éléments d'un tableau sont accessibles avec des crochets, et l'indexation commence à zéro.
$ echo '[10, 20, 30]' | jq '.[1]'
20
Un filtre peut construire un tableau en entourant une expression de [ et ]
avec une liste connue d'éléments :
jq -n '[1, 2, 3]'
pour collecter un flux d'éléments : par exemple,
range est une fonction qui produit un flux de nombres
$ jq -n 'range(10; 70; 15)'
10
25
40
55
Utiliser [] rassemble les résultats de l'expression dans un tableau
$ jq -c -n '[range(10; 70; 15)]'
[10,25,40,55]
La virgule n'est pas seulement une syntaxe qui sépare les éléments d'un tableau. La virgule est un opérateur qui joint des flux.
Par exemple, [1, 2, 3] est un filtre qui utilise le constructeur de tableau [] pour rassembler le résultat de la jonction des trois expressions 1, 2 et 3.
As-tu remarqué les points-virgules dans range(10; 70; 15) plus haut ?
Comme les virgules ont un rôle bien précis dans le langage jq, les fonctions qui prennent plusieurs arguments utilisent des points-virgules pour séparer les arguments.
Une introduction rapide aux objets.
Comme dans beaucoup de langages de programmation, on utilise des points pour accéder aux propriétés d'un objet
$ echo '{"foo": {"bar": "qux"}}' | jq '.foo.bar'
"qux"
On peut aussi utiliser des crochets pour les objets, mais il faut alors des guillemets pour les littéraux de type string. C'est une méthode pour manipuler des clés contenant des espaces.
$ echo '{"foo bar": "qux"}' | jq '.["foo bar"]'
"qux"
Tu peux construire un objet avec {} et des paires key: value.
Les guillemets ne sont pas nécessaires autour des clés qui sont des strings « simples ».
jq -n '{question: (6 * 9), answer: 42}'
affiche
{
"question": 54,
"answer": 42
}
Pour traiter la clé comme une expression, il faut l'entourer de parenthèses (l'exemple suivant produit lui aussi le même résultat que ci-dessus).
echo '[["question", "answer"], [54, 42]]' \
| jq '{(.[0][0]): .[1][0], (.[0][1]): .[1][1]}'
Il est assez courant de vouloir extraire un sous-ensemble de clés d'un grand objet.
Par exemple, pour extraire id et name de
{
"id": 101,
"name": "alpha widget",
"specifications": {...}
}
On pourrait écrire
{id: .id, name: .name}
Mais c'est tellement courant qu'il existe une syntaxe raccourcie pour cela :
{id, name}
Par exemple, étant donné un fichier file.json contenant
{
"key1": "value1",
"key2": [5, 15, 25]
}
Calculons la longueur du tableau key2 :
$ jq '.key2 | length' file.json
3
On envoie la sortie de l'expression .key2 dans l'entrée de length au moyen d'un pipe ; sans surprise, cela produit le nombre d'éléments du tableau.
C'est un aspect de jq auquel il faut un peu s'habituer :
la plupart des fonctions (mais pas toutes) se comportent comme des filtres, où l'on passe les données à l'entrée du filtre plutôt que comme argument.
Dans cet exemple, les données JSON d'entrée sont ignorées et n'ont aucun impact sur la sortie :
$ echo '{"answer": 42}' | jq '6 * 9'
54
Un filtre peut produire plus d'une valeur.
Par exemple, le filtre .[] produit chaque élément d'un tableau comme une valeur distincte :
$ jq -n -c '[1, 2, 3]'
[1,2,3]
$ jq -n -c '[1, 2, 3] | .[]'
1
2
3
Envoyer la sortie d'un tel filtre dans un autre exécute le 2ᵉ filtre pour chaque valeur :
$ jq -n -c '[1, 2, 3] | .[] | . * 2'
2
4
6
C'est comme une itération implicite.
Une fois que tu as compris cette technique, tu réalises que des filtres jq très puissants peuvent être très concis.
Les parenthèses servent à regrouper des sous-expressions pour imposer l'ordre des opérations, comme dans les autres langages.
En jq, le besoin d'en utiliser peut sembler un peu surprenant.
Par exemple, imaginons que l'on veuille construire un tableau à 2 éléments : la racine carrée de 9, et e élevé à la puissance 1.
Les deux expressions individuelles sont 9 | sqrt et 1 | exp.
On s'attend à ce que la sortie soit le tableau [3, 2.7...]
$ jq -n '[ 9|sqrt, 1|exp ]'
[
20.085536923187668,
2.718281828459045
]
Pourquoi n'a-t-on pas obtenu ce qu'on attendait ? jq interprète cela comme ceci :
[ ((9|sqrt), 1) | exp ]
jq construit un flux de deux éléments (3 et 1) qui sont chacun donnés à exp.
Il faut s'assurer que exp ne reçoit qu'un seul nombre en entrée.
Autrement dit, il faut imposer que le pipe soit évalué avant la virgule.
$ jq -n '[ 9|sqrt, (1|exp) ]'
[
3,
2.718281828459045
]
D'après le manuel
jq prend en charge le même ensemble de types de données que JSON : nombres, strings, booléens, tableaux, objets (qui, en jargon JSON, sont des tables de hachage dont les clés ne peuvent être que des strings), et « null ».
Tu en apprendras plus à leur sujet dans les exercices suivants.
Les espaces n'ont pas d'importance en jq.
Utilise des espaces, des tabulations ou des retours à la ligne comme bon te semble pour mettre ton code en forme.
On ne connaît aucun guide de style jq existant.
Les valeurs en jq sont immuables.
Les filtres qui modifient une valeur produisent une nouvelle valeur.
Cela implique que jq n'a pas de variables globales :
il faut s'habituer à transmettre l'état d'un filtre à un autre.
Les valeurs false et null sont considérées comme fausses. Toute autre valeur
(y compris le nombre zéro et une string, un tableau ou un objet vide) est vraie.
Sans entrer dans les détails (les fonctions feront l'objet d'un autre exercice), voici quelques fonctions natives utiles :
Étant donné un tableau en entrée, produit le nombre d'éléments de ce tableau.
$ jq -n '[10, 20, 30, 40] | length'
4
Cet opérateur se comporte différemment selon le type de ses opérandes : il additionne des nombres, il concatène des strings, il assemble des tableaux, il fusionne des objets.
$ jq -c -n '
3 + 4,
"foo" + "bar",
["a", "b"] + ["c"],
{"m": 10} + {"n": 20}
'
7
"foobar"
["a","b","c"]
{"m":10,"n":20}
add est une fonction qui prend un tableau et renvoie un élément obtenu en additionnant tous ses éléments selon les règles de +.
[1, 2, 3] | add produit 6.
Étant donné un tableau en entrée et un filtre en argument, produit un tableau où le filtre est appliqué à chaque élément
$ jq -c -n '[10, 20, 30, 40] | map(. / 5)'
[2,4,6,8]
Étant donné une certaine entrée et un filtre en argument :
null, vraiment aucune sortie)Par exemple, étant donné quelques nombres, on sélectionne ceux qui sont divisibles par 3
$ jq -n 'range(10) | select(. % 3 == 0)'
0
3
6
9
Rappelle-toi que range produit un flux de nombres.
select sera appelé une fois pour chaque nombre.
Seuls les nombres qui « passent » l'expression sont produits.
Il est souvent nécessaire de sélectionner des éléments d'un tableau. Il existe plusieurs façons de procéder.
Avec l'entrée ["Anne", "Bob", "Cathy", "Dave"], sélectionne les prénoms dont la longueur vaut 4.
utiliser map et select ensemble
map(select(length == 4))
éclater le tableau en éléments, appliquer select sur ce flux, puis rassembler les résultats
[ .[] | select(length == 4) ]
Les commentaires commencent par le caractère # et se poursuivent jusqu'à la fin de la ligne.