On trouve facilement des exemples sur des problématiques atomiques sur jq sur de nombreux sites, comme sur StackOverflow, mais il est plus difficile de trouver de la documentation permettant de garder le flux initial.
Ce billet vous propose un exemple ou lâon modifie assez fortement une partie de la structure initiale Ă©tape par Ă©tape.
Présentation
- LâidĂ©e ici est de distribuer des donnĂ©es afin dâaplatir une structure JSON.
- Pour que lâexemple soit complet, on va Ă©galement :
- Garder la structure dâorigine et donc ne modifier que le sous-arbre que lâon souhaite aplatir,
- Récupérer une valeur ailleurs dans la structure (
.zone."@"
) et lâinjecter dans chacune des entrĂ©es crĂ©e.
{
"types": {
"A": [
{ "source": "abc", "target": "192.168.1.1" },
{ "source": "xyz", "target": "192.168.1.2" }
],
"NS": [{ "source": "@", "target": "ns1.services.cclaude.rocks." }]
},
"zone": {
"@": "test.cclaude.rocks",
"Autre": "chose"
}
}
{
"types": [
{
"type": "A", // On reporte le type
"zone": "test.cclaude.rocks", // On reporte la zone
"source": "abc",
"target": "192.168.1.1"
},
{
"type": "A", // On reporte le type
"zone": "test.cclaude.rocks", // On reporte la zone
"source": "xyz",
"target": "192.168.1.2"
},
{
"type": "NS", // On reporte le type
"zone": "test.cclaude.rocks", // On reporte la zone
"source": "@",
"target": "ns1.services.cclaude.rocks."
}
],
"zone": { // On garde les autres objets tel quel
"@": "test.cclaude.rocks",
"Autre": "chose"
}
}
Premier jet
La premiĂšre chose Ă faire est Ă sauvegarder la valeur de lâobjet zone que lâon souhaite reporter dans chacune des entrĂ©es du tableau, types:
.zone."@" as $ZONE
Ensuite, dans un premier temps on travaille, uniquement sur ce tableau :
.type
Afin de pouvoir récupérer le nom de la clé et sa valeur associée, on va utiliser la fonction to_entries
.
Puisquâon veut faire un traitement sur lâensemble des couples (key,value), on va le « dĂ©structurer » Ă lâaide de []
:
to_entries[]
à ce moment, on est dans une boucle : « pour chaque (key,value) faire »
On sauvegarde alors la clé dans $K
:
.key as $K
on peut maintenant reconstruire la clé couple (key,value) :
{
"key": $K, // On remet la clé
"value": ... // Nouvel objet
}
Il faut construire un nouvel objet qui est un tableau, on va donc traiter chaque entrĂ©e du tableau Ă lâaide de la fonction map()
et pour chaque entrée ajouter la clé comme type et la valeur de $ZONE comme zone :
.value |
map( { "type": $K, "zone": $ZONE } + . )
Le code complet depuis un terminal bash :
echo '{"types":{"A":[{"source":"abc","target":"192.168.1.1"},{"source":"xyz","target":"192.168.1.2"}],"NS":[{"source":"@","target":"ns1.services.cclaude.rocks."}]},"zone":{"@":"test.cclaude.rocks","Autre":"chose"}}' | jq '.zone."@" as $ZONE | .types | to_entries[] | .key as $K | { "key": $K, "value": (.value | map( { "type": $K, "zone": $ZONE } + . )) }'
Le filtre jq formaté :
.zone."@" as $ZONE |
.types |
to_entries[] |
.key as $K |
{
"key": $K,
"value": (.value | map( { "type": $K, "zone": $ZONE } + . ))
}
Le code sur jqplay.org
Voici ce que cette premiÚre version donne :
{
"key": "A",
"value": [
{
"type": "A",
"zone": "test.cclaude.rocks",
"source": "abc",
"target": "192.168.1.1"
},
{
"type": "A",
"zone": "test.cclaude.rocks",
"source": "xyz",
"target": "192.168.1.2"
}
]
}
{
"key": "NS",
"value": [
{
"type": "NS",
"zone": "test.cclaude.rocks",
"source": "@",
"target": "ns1.services.cclaude.rocks."
}
]
}
On peut sembler loin du résultat espérer, mais on a fait le plus gros du travail.
On raffine le premier jet avec from_entries
On va maintenant faire lâopĂ©ration inverse de la fonction to_entries
Ă lâaide de from_entries
.
Le code complet depuis un terminal bash :
echo '{"types":{"A":[{"source":"abc","target":"192.168.1.1"},{"source":"xyz","target":"192.168.1.2"}],"NS":[{"source":"@","target":"ns1.services.cclaude.rocks."}]},"zone":{"@":"test.cclaude.rocks","Autre":"chose"}}' | jq '.zone."@" as $ZONE | .types | [ to_entries[] | .key as $K | { "key": $K, "value": (.value | map( { "type": $K, "zone": $ZONE } + . )) } ] | from_entries'
.zone."@" as $ZONE |
.types |
[
to_entries[] |
.key as $K |
{
"key": $K,
"value": (
.value |
map(
{ "type": $K, "zone": $ZONE } + .
)
)
}
] |
from_entries
- Voir sur jqplay.org.
{
"A": [
{
"type": "A",
"zone": "test.cclaude.rocks",
"source": "abc",
"target": "192.168.1.1"
},
{
"type": "A",
"zone": "test.cclaude.rocks",
"source": "xyz",
"target": "192.168.1.2"
}
],
"NS": [
{
"type": "NS",
"zone": "test.cclaude.rocks",
"source": "@",
"target": "ns1.services.cclaude.rocks."
}
]
}
On optimise un peu le code avec with_entries
On remarque la séquence [ to_entries[] | ⊠| from_entries ]
hors câest justement ce que fait la fonction with_entries( ⊠)
, on va donc simplement remplacer la séquence par cette fonction.
- Voir le manuel de JQ
Le code complet depuis un terminal bash :
echo '{"types":{"A":[{"source":"abc","target":"192.168.1.1"},{"source":"xyz","target":"192.168.1.2"}],"NS":[{"source":"@","target":"ns1.services.cclaude.rocks."}]},"zone":{"@":"test.cclaude.rocks","Autre":"chose"}}' | jq '.zone."@" as $ZONE | .types | with_entries( .key as $K | { "key": $K, "value": (.value | map( { "type": $K, "zone": $ZONE } + . )) })'
Le filtre jq formaté :
.zone."@" as $ZONE |
.types |
with_entries(
.key as $K |
{
"key": $K,
"value": (
.value |
map(
{ "type": $K, "zone": $ZONE } + .
)
)
}
)
Le résultat est inchangé, voir sur jqplay.org.
La séquence
[ to_entries[] | ⊠| from_entries ]
peut sâĂ©crire avec la fonction
with_entries( ⊠)
Affinage du tableau
Maintenant, il reste une partie un peu délicate qui consiste à supprimer le niveau juste dessous .types et à mettre tous les éléments des sous tableaux dans un seul tableau.
Pour cela juste aprĂšs le filtre .types
on va créer un tableau, mettre tout le code restant dans ce tableau :
.types | [ ⊠]
AprĂšs la fonction with_entries
, on va faire un to_entries
et « déstructurer » 2 niveaux de tableau :
.types | [ with_entries( ⊠) | to_entries[].value[] ]
Le code complet depuis un terminal bash :
echo '{"types":{"A":[{"source":"abc","target":"192.168.1.1"},{"source":"xyz","target":"192.168.1.2"}],"NS":[{"source":"@","target":"ns1.services.cclaude.rocks."}]},"zone":{"@":"test.cclaude.rocks","Autre":"chose"}}' | jq '.zone."@" as $ZONE | .types | [ with_entries( .key as $K | { "key": $K, "value": (.value | map( { "type": $K, "zone": $ZONE } + . )) }) | to_entries[].value[] ]'
Le filtre jq formaté :
.zone."@" as $ZONE |
.types |
[
with_entries(
.key as $K |
{
"key": $K,
"value": (
.value |
map(
{ "type": $K, "zone": $ZONE } + .
)
)
}
) | to_entries[].value[]
]
- Voir sur jqplay.org.
[
{
"type": "A",
"zone": "test.cclaude.rocks",
"source": "abc",
"target": "192.168.1.1"
},
{
"type": "A",
"zone": "test.cclaude.rocks",
"source": "xyz",
"target": "192.168.1.2"
},
{
"type": "NS",
"zone": "test.cclaude.rocks",
"source": "@",
"target": "ns1.services.cclaude.rocks."
}
]
Le code final
Il ne reste plus quâĂ faire la mĂȘme chose tout en conservant la structure initiale :
Lâastuce consiste tout simplement Ă affecter le rĂ©sultat obtenu plus haut comme suit (sans omettre les parenthĂšses) :
.types=( ⊠)
Le code complet depuis un terminal bash :
echo '{"types":{"A":[{"source":"abc","target":"192.168.1.1"},{"source":"xyz","target":"192.168.1.2"}],"NS":[{"source":"@","target":"ns1.services.cclaude.rocks."}]},"zone":{"@":"test.cclaude.rocks","Autre":"chose"}}' | jq '.zone."@" as $ZONE | .types=(.types | [ with_entries( .key as $K | { "key": $K, "value": (.value | map( { "type": $K, "zone": $ZONE } + . )) }) | to_entries[].value[] ])'
Le filtre jq formaté :
.zone."@" as $ZONE |
.types=(
.types |
[
with_entries(
.key as $K |
{
"key": $K,
"value": (
.value |
map(
{ "type": $K, "zone": $ZONE } + .
)
)
}
) |
to_entries[].value[]
]
)
- Voir sur jqplay.org.
{
"types": [
{
"type": "A",
"zone": "test.cclaude.rocks",
"source": "abc",
"target": "192.168.1.1"
},
{
"type": "A",
"zone": "test.cclaude.rocks",
"source": "xyz",
"target": "192.168.1.2"
},
{
"type": "NS",
"zone": "test.cclaude.rocks",
"source": "@",
"target": "ns1.services.cclaude.rocks."
}
],
"zone": {
"@": "test.cclaude.rocks",
"Autre": "chose"
}
}
Liens
- Les fonctions
to_entries
,from_entries
etwith_entries
expliquées dans le manuel de JQ, - La FAQ de jq en anglais,
- Le manuel de jq en anglais (Vous pouvez choisir la version de jq).
኿