Lorsque l’on souhaite convertir des données brutes en JSON avec jq on se retrouve souvent confronté un souci dès la première étape et comme débutant on est obligé de multiplier les appels à jq.
Limiter les appels à « jq »
On va considérer le fichier texte suivant :
a
b
c
Que l’on va simuler à l’aide du code suivant :
printf 'a\nb\nc'
Le code JSON attendu étant le suivant :
[
"a",
"b",
"c"
]
La première idée est d’utiliser les drapeaux --raw-input
et --slurp
comme suit :
printf 'a\nb\nc' | jq --raw-input --slurp
Mais on se retrouve avec le résultat suivant :
"a\nb\nc"
En effet, dans le traitement des trapeaux --raw-input
et --slurp
se font en même temps, et non pas de manière séquentielle.
Si on utilise deux appels successifs, on a bien ce que l’on attendait :
printf 'a\nb\nc' | jq --raw-input | jq --slurp
La première commande jq transforme les lignes en chaîne JSON, la seconde construit un tableau de chaîne.
[
"a",
"b",
"c"
]
Pour faire la même chose en une seule commande jq, voici comment faire :
printf 'a\nb\nc' | jq --raw-input --null-input '[ inputs ]'
- Le drapeau
--raw-input
transforme chaque ligne du fichier en chaîne JSON, - Le drapeau
--null-input
indique de ne pas attendre de valeur depuis l’entrée standard (puisqu’on va utiliser la commandeinputs
pour prendre en compte toutes les données en entrée). - La commande
inputs
retourne l’entrée standard (une liste de chaîne JSON, grâce à--raw-input
) et comme elle est entourée par un début[
et une fin]
de tableau : cette liste est mise dans un tableau : cela correspond simplement à ce que fait le drapeau--slurp
, mais ici on s'assure que l'opération slurp soit traitée après l'opération raw-input (et non pas en même temps).
[
"a",
"b",
"c"
]
Exemple concret
Considérons le texte suivant :
a=A
b=B
c=C
Que l’on souhaite convertir en JSON comme suit :
{
"a": "A",
"b": "B",
"c": "C"
}
Voici deux exemples de code qui produisent cette sortie :
printf 'a=A\nb=B\nc=C' |
jq --raw-input --null-input '
[ inputs ] |
map( split("=") | { (.[0]) : .[1] } ) |
reduce .[] as $x ({}; . + $x)
'
On peut éviter le map
, en effectuant le traitement directement lors de la création du tableau, comme suit :
printf 'a=A\nb=B\nc=C' |
jq --raw-input --null-input '
[
inputs | split("=") | { (.[0]): .[1] }
] |
reduce .[] as $x ({}; . + $x)
'
Explications :
Pour chaque chaîne de type 'x=X
on effectue les traitements suivants:
split("=")
transforme la chaîne en un tableau en utilisant le signe=
comme séparateur… S’il y a un caractère=
dans la ligne cela créera 2 éléments, mais la règle que pour n caractères=
, on obtiendran + 1
éléments.- La séquence
{ (.[0]): .[1] }
remplace le tableau que l’on suppose n’avoir que 2 éléments (.[0]
et.[1]
) par un objet JSON dont la clé sera le premier élément du tableau et la valeur sera le second élement. Ici on comprend que ni la clé, ni la valeur ne doit contenir de caractère=
pour que cela fonctionne comme attendu.
à ce moment, on a cela :
[{"a":"A"},{"b":"B"},{"c":"C"}]
A savoir un tableau d’objets JSON.
La séquence reduce .[] as $x ({}; . + $x)
ayant pour effet de réduire ce tableau en un seul objet JSON.
reduce
EXP as
$var (INIT
; UPDATE
)
qui se traduit par :
Pour chaque résultat produit par EXP, accumuler le résultat donner par UPDATE en partant de INIT.
- EXP est ici l'expression:
.[]
à savoir la liste des objets JSON{"a":"A"}
puis{"b": "B"}
et enfin{"c": "C"}
. Ces valeurs étant successivement mises dans la variable$x
. - INIT est l’initialisation d’un objet vide
{}
: On part donc d’un objet vide. - UPDATE est la règle de mise à jour
. + $x
:.
le flux en court, c’est ici l’objet qu’on est en train de construire auquel on ajoute d’un nom courant.
Concrètement, cette dernière partie de code permet de transformer le tableau :
[{"a":"A"},{"b":"B"},{"c":"C"}]
en l’objet :
{"a":"A","b":"B","c":"C"}
Liens
ᦿ