Parcours
/
jq
jq
/
Programme
/
Les bases
Le

Les bases en jq

1 exercice

À propos de Les bases

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.

Une introduction rapide aux options de ligne de commande de 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.

Filtres et pipes

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.

Le filtre identité : .

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
]

Tableaux

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 est un opérateur

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.

Objets

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]}'
Note

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}

Pipelines

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.

Caution

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.

Les filtres peuvent ignorer leur entrée

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

Les filtres peuvent produire des flux de données

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.

Parenthèses

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
]

Types

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.

Espaces

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.

Valeurs immuables

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.

La « truthiness »

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.

Fonctions et opérateurs

Sans entrer dans les détails (les fonctions feront l'objet d'un autre exercice), voici quelques fonctions natives utiles :

  • length

    É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.

  • map

    É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]
    
  • select

    Étant donné une certaine entrée et un filtre en argument :

    • si le filtre appliqué à l'argument donne une valeur vraie, produit l'entrée inchangée
    • sinon, ne produit rien (pas la valeur 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) ]
      

Commentaires

Les commentaires commencent par le caractère # et se poursuivent jusqu'à la fin de la ligne.

Modifie via GitHub Le lien s'ouvre dans une nouvelle fenêtre ou un nouvel onglet

Apprends Les bases