Afin de rĂ©duire la taille de certains fichiers JSON, il peut ĂȘtre intĂ©ressant dâutiliser la notion de valeur par dĂ©faut.
On va voir comment utiliser un filtre jq pour faire cela Ă moindres frais.
Présentation de la problématique
Imaginons le fichier JSON suivant : (fichier1.json
)
{
"context1": {
"config": {
"cle1": "valeur-context-1",
"cle2": "valeur-commune-2",
"cle3": "valeur-commune-3",
"cle4": "valeur-commune-4",
"cle5": "valeur-commune-5"
}
},
"context2": {
"config": {
"cle1": "valeur-commune-1",
"cle2": "valeur-context-2",
"cle3": "valeur-commune-3",
"cle4": "valeur-commune-4"
}
},
"context3": {
"config": {
"cle1": "valeur-commune-1",
"cle2": "valeur-commune-2",
"cle3": "valeur-commune-3",
"cle4": "valeur-commune-4"
}
}
}
On constate quâil est possible dâavoir la mĂȘme information comme suit : (fichier2.json
)
{
"config": {
"cle1": "valeur-commune-1",
"cle2": "valeur-commune-2",
"cle3": "valeur-commune-3",
"cle4": "valeur-commune-4"
},
"context1": {
"config": {
"cle1": "valeur-context-1",
"cle5": "valeur-commune-5"
}
},
"context2": {
"config": {
"cle2": "valeur-context-2"
}
},
"context3": {
"config": {}
}
}
Cette version est plus courte, mais Ă©galement plus claire, puisquâelle met en Ă©vidence les valeurs communes. On dira quâon a factorisĂ© la configuration.
Dans le premier cas, pour obtenir la configuration dâun « contexte », on Ă©crira : đčjq-play
jq '.context1.config' fichier1.json
{
"cle1": "valeur-context-1",
"cle2": "valeur-commune-2",
"cle3": "valeur-commune-3",
"cle4": "valeur-commune-4"
}
Comment obtenir le mĂȘme rĂ©sultat Ă partir du second fichier ? đčjq-play
jq '.config + .context1.config' fichier2.json
On peut reconstruire la structure initiale, comme suit : đčjq-play
jq '.config as $D | { context1: { config: ($D + .context1.config) } }' fichier2.json
{
"context1": {
"config": {
"cle1": "valeur-context-1",
"cle2": "valeur-commune-2",
"cle3": "valeur-commune-3",
"cle4": "valeur-commune-4"
}
}
}
Pour reconstruire tout le fichier : đčjq-play
jq '.config as $D |
{
context1: { config: ($D + .context1.config) },
context2: { config: ($D + .context2.config) },
context3: { config: ($D + .context3.config) }
}' fichier2.json
-
Explications
Détaillons le filtre jq de ce dernier cas qui utilise le mot clé
as
 :.config as $D | { context1: { config: ($D + .context1.config) }, context2: { config: ($D + .context2.config) }, context3: { config: ($D + .context3.config) } }
.config as $D
 : On applique le filtre.config
et on le met dans la variable$D
(du coup, le flux courant reste inchangé)- `|' : Le flux courant restant inchangé, on continue avec fichier initial.
{ ⊠}
 : On dĂ©finit un nouvel objetcontext1: { config: ⊠}, âŠ
 : On reconstruit la structure($D + .context1.config)
 : pour la valeur de chaque clé.config
on va prendre le contenu de$D
et on ajoute ou modifie+
avec lâobjet.context.config
du contexte courant.
GĂ©nĂ©ralisons au cas oĂč il y a dâautres valeurs dans le fichier
Si vous Ă©crivez un nouveau code, peut-ĂȘtre le plus simple sera de prendre en compte cette notion de valeur par dĂ©faut, mais dans un traitement existant, il faut mieux revenir au format initial.
Pour lâexemple, on ajoutera uniquement une clĂ© name
avec sa valeur, mais cela peut ĂȘtre quelque chose de beaucoup plus compliquĂ©.
Les données complÚtes : (fichier11.json
)
{
"context1": {
"config": {
"cle1": "valeur-context-1",
"cle2": "valeur-commune-2",
"cle3": "valeur-commune-3",
"cle4": "valeur-commune-4"
},
"name": "context-1"
},
"context2": {
"config": {
"cle1": "valeur-commune-1",
"cle2": "valeur-context-2",
"cle3": "valeur-commune-3",
"cle4": "valeur-commune-4"
},
"name": "context-2"
},
"context3": {
"config": {
"cle1": "valeur-commune-1",
"cle2": "valeur-commune-2",
"cle3": "valeur-commune-3",
"cle4": "valeur-commune-4"
}
},
"name": "context-3"
}
Les données misent en facteur : (fichier12.json
)
{
"config": {
"cle1": "valeur-commune-1",
"cle2": "valeur-commune-2",
"cle3": "valeur-commune-3",
"cle4": "valeur-commune-4"
},
"context1": {
"config": {
"cle1": "valeur-context-1"
},
"name": "context-1"
},
"context2": {
"config": {
"cle2": "valeur-context-2"
},
"name": "context-2"
},
"context3": {
"config": {},
"name": "context-3"
}
}
Pour reconstruire le premier fichier Ă partir du second, on utilisera : đčjq-play
jq '.config as $D |
del( .config ) |
.context1.config = ($D + .context1.config) |
.context2.config = ($D + .context2.config) |
.context3.config = ($D + .context3.config)
' fichier12.json
-
Explications
Détaillons le filtre jq :
.config as $D | del( .config ) | .context1.config = ($D + .context1.config) | .context2.config = ($D + .context2.config) | .context3.config = ($D + .context3.config)
.config as $D
 : On applique le filtre.config
et on le met dans la variable$D
(du coup, le flux courant reste inchangé)- `|' : Le flux courant restant inchangé, on continue avec fichier initial.
.context1.config = âŠ
 : On applique une nouvelle valeur Ă lâobjet.context1.config
.($D + .context1.config)
 : pour la valeur de chaque clé.config
on va prendre le contenu de$D
et on ajoute ou modifie+
avec lâobjet.context.config
du contexte courant.- et on reproduit pour les 2 autres objets
.context2
et.context3
.
Généralisation aux tableaux
Dans les exemples prĂ©cĂ©dant, le choix de lâutilisation de .context1
, .context2
, et .context3
nâest probablement pas une bonne idĂ©e.
On va donc regarder cela pour le cas oĂč lâon souhaite factoriser pour un tableau.
Considérons ce fichier : (fichier22.json
)
{
"config": {
"cle1": "valeur-commune-1",
"cle2": "valeur-commune-2",
"cle3": "valeur-commune-3",
"cle4": "valeur-commune-4"
},
"contexts": [
{
"config": {
"cle1": "valeur-context-1"
},
"name": "context-1"
},
{
"config": {
"cle2": "valeur-context-2"
},
"name": "context-2"
},
{
"config": {},
"name": "context-3"
}
]
}
Dans ce cas, on a un tableau de contextes, on peut obtenir toutes les configurations avec un code assez compactes, mais en perdant les autres valeurs (cependant, cela peut ĂȘtre suffisant pour votre cas dâusage). đčjq-play
jq '.config as $D | [ $D + .contexts[].config ]' fichier22.json
Mais on peut facilement reconstruire le fichier complet Ă lâaide de : đčjq-play
jq '.config as $D |
del( .config ) | .contexts[] | [ .config = ($D + .config) ]
' fichier22.json
Factorisation sur plusieurs niveaux
Imaginons que lâon ait la structure suivante : (fichier32.json
)
{
"config": {
"cle1": "valeur-globale-1",
"cle2": "valeur-globale-2",
"cle3": null
},
"context1": {
"config": {
"cle1": "valeur-context-1",
"cle2": null,
"cle4": "valeur-context-4"
},
"name": "context-1",
"sous-contexts": [
{
"config": {
"cle1": "sous-valeur-context-1"
},
"name": "sous-context-1"
},
{
"config": {
"cle3": "sous-valeur-context-3"
},
"name": "sous-context-2"
},
{
"config": {},
"name": "sous-context-3"
}
]
},
"context2": {
"config": {
"cle2": "valeur-context-2"
},
"name": "context-2",
"sous-contexts": [
{
"config": {},
"name": "sous-context-1"
},
{
"config": {
"cle2": "valeur-sous-context-2"
},
"name": "sous-context-2"
}
]
}
}
Les configurations du second contexte sâobtiennent comme suit : đčjq-play
jq '(.context2.config + .config ) as $config | [ .context2."sous-contexts"[] | $config + .config ]' fichier32.json
Avec un peu de bash, on peut avoir toutes les configurations :
for C in context1 context2 ; do
jq --arg context "$C" '(.[$context].config + .config ) as $config | [ .[$context]."sous-contexts"[] | $config + .config ]' fichier32.json
done
Pour obtenir une configuration donnĂ©e : đčjq-play
jq '(.context2.config + .config ) as $config | .context2."sous-contexts"[] | select( .name =="sous-context-2" ) | $config + .config' fichier32.json
On peut généraliser :
function show_config {
local -r objet_context="$1"
local -r nom_sous_context="$2"
jq --arg context "${objet_context}" \
--arg name "${nom_sous_context}" '
(.context2.config + .config ) as $config | .context2."sous-contexts"[] | select( .name == $name ) | $config + .config
' fichier32.json
}
show_config 'context2' 'sous-context-2'
Ce qui dans notre cas donnera :
{
"cle2": "valeur-sous-context-2",
"cle1": "valeur-globale-1",
"cle3": null
}
On peut éventuellement améliorer le filtre pour supprimer les valeurs à null
en ajoutant :
⊠| [to_entries[] | select( .value != null )] | from_entries
Je me suis cassé les dents sur la création du fichier « dé-factorisé » avec du code jq pur, si vous avez une piste élégante, je vous invite à laisser un commentaire.
Liens
- Les autres billets sur la commande « jq »,
- Une documentation en français,
- Le site jq-play pour tester vos filtres en ligne.
- La FAQ de jq en anglais,
- Le manuel de jq en anglais (Vous pouvez choisir la version de jq).
኿