Voici comment combiner deux fichiers JSON pour obtenir un fichier de synthĂšse sur la base dâune clĂ© commune.
LâidĂ©e est de partir de deux fichiers JSON contenant des donnĂ©es disjointes, mais ayant une clĂ© commune et de construire un fichier contenant les donnĂ©es des deux fichiers.
Prenons deux fichiers JSONÂ :
[
{ "A": "a0", "B": "b0" },
{ "A": "a1", "B": "b1" },
{ "A": "a2", "B": "b2" },
{ "A": "a3", "B": "b3" }
]
[
{ "A": "a0", "C": "c0" },
{ "A": "a2", "C": "c2" },
{ "A": "a4", "C": "c4" },
{ "A": "a8", "C": "c6" },
{ "A": "a6", "C": "c8" }
]
Dans le cas ci-dessus :
- La clé commune est le champ
A
. - Le premier fichier renseigne le contenu du champ
B
, - Le premier fichier renseigne le contenu du champ
C
, - Les données sont incomplÚtes.
Voici la formule magique :
jq --slurp 'flatten(1) | group_by(.A) | map(reduce .[] as $x ({}; . * $x))' fichier1.json fichier2.json
Explications :
Commençons par voir ce que fait :
jq -c '.' C.json B.json # le -c permet dâavoir une vue compacte du rĂ©sultat et de voir les « lots » traitĂ©s par jq.
[{"A":"a0","C":"c0"},{"A":"a2","C":"c2"},{"A":"a4","C":"c4"},{"A":"a8","C":"c6"},{"A":"a6","C":"c8"}]
[{"A":"a0","B":"b0"},{"A":"a1","B":"b1"},{"A":"a2","B":"b2"},{"A":"a3","B":"b3"}]
Ici on comprend que les deux fichiers seront traitĂ©s sĂ©parĂ©ment et ce nâest pas ce que lâon souhaite, pour rĂ©soudre cela nous utiliserons le paramĂštre --slurp
.
jq --slurp -c '.' C.json B.json
[[{"A":"a0","C":"c0"},{"A":"a2","C":"c2"},{"A":"a4","C":"c4"},{"A":"a8","C":"c6"},{"A":"a6","C":"c8"}],[{"A":"a0","B":"b0"},{"A":"a1","B":"b1"},{"A":"a2","B":"b2"},{"A":"a3","B":"b3"}]]
Cette fois, on a bien une seule ligne, mais on a un tableau contenant deux éléments correspondant aux deux fichiers. Cependant nous souhaitons obtenir un seul tableau à la fin (comme dans les fichiers initiaux), pour cela on va commencer le filtre jq par la commande flatten
 :
jq --slurp -c 'flatten' C.json B.json
[{"A":"a0","C":"c0"},{"A":"a2","C":"c2"},{"A":"a4","C":"c4"},{"A":"a8","C":"c6"},{"A":"a6","C":"c8"},{"A":"a0","B":"b0"},{"A":"a1","B":"b1"},{"A":"a2","B":"b2"},{"A":"a3","B":"b3"}]
Maintenant nous avons un flux JSON avec toutes nos données, mais si on considÚre le champ A
comme une clé, il y a des doublons.
Complément sur flatten
, flatten(profondeur)
 :
Le filtre flatten
prend en entrĂ©e un tableau de tableaux imbriquĂ©s, et produit un tableau plat dans lequel tous les tableaux Ă lâintĂ©rieur du tableau original ont Ă©tĂ© remplacĂ©s rĂ©cursivement par leurs valeurs. Vous pouvez lui passer un argument pour spĂ©cifier le nombre de niveaux dâimbrication Ă aplanir.
jq -c 'flatten' <<<'[1, [2], [[3]]]'
[1,2,3]
jq -c 'flatten(1)' <<<'[1, [2], [[3]]]'
[1,2,[3]]
jq -c 'flatten(2)' <<<'[1, [2], [[3]]]'
[1,2,3]
Mettons les doublons en Ă©vidence Ă lâaide de la commande group_by
 :
jq --slurp -c 'flatten(1) | group_by(.A)' C.json B.json
[
[{"A":"a0","C":"c0"},{"A":"a0","B":"b0"}],
[{"A":"a1","B":"b1"}],
[{"A":"a2","C":"c2"},{"A":"a2","B":"b2"}],
[{"A":"a3","B":"b3"}],[{"A":"a4","C":"c4"}],
[{"A":"a6","C":"c8"}],[{"A":"a8","C":"c6"}]
]
Il ne reste plus quâĂ faire un MapReduce pour obtenir le rĂ©sultat attendu :
jq --slurp -c 'flatten | group_by(.A) | map(reduce .[] as $x ({}; . * $x))' C.json B.json
- La commande
map(x)
est Ă©quivalente Ă[.[] | x]
, cela indique que pour la suite on traitera les éléments du tableau un par un et non pas le tableau dans sa globalité. - La commande
reduce
permet dâitĂ©rer sur les Ă©lĂ©ments qui arrive (donc, du fait la commandemap
précédente, des éléments du tableau.
Complément sur reduce
 :
Une façon simple de voir la commande reduce
est dâanalyse la ligne suivante :
jq 'reduce .[] as $item (0; . + $item)' <<<'[10,2,5,3,22]'
Prenons le flux JSON [10,2,5,3,22]
et en partant de la valeur 0
, on ajoute les valeurs du tableau dâentrĂ©e. Cela fait tout simplement la somme de toutes les entrĂ©es du tableau.
Complément sur *
 :
La multiplication de deux objets consiste en une fusion de maniĂšre rĂ©cursive : cela fonctionne comme une addition mais si les deux objets contiennent une valeur pour la mĂȘme clĂ©, et que les valeurs sont des objets, les deux sont fusionnĂ©s avec la mĂȘme stratĂ©gie.
Dans le cas étudié, on utilise reduce .[] as $x ({}; . * $x)
:
On part donc de lâĂ©lĂ©ment vide {}
et on fusionne (*
) tous les éléments du tableau obtenu par group_by
ensemble et câĂ©tait le rĂ©sultat souhaitĂ©.
Quelques précisions
-
Pour aller plus loin
Voici un exemple complet avec des fichiers de départ légÚrement différents, et surtout avec des données inconsistantes dans le champ nommé
X
.Regardons comment cela se comporte :
cat <<EOF >B.json [ { "A": "a0", "B": "b0" }, { "A": "a1", "B": "b1", "X": "x1b" }, { "A": "a2", "B": "b2", "X": "x2b" }, { "A": "a3", "B": "b3" } ] EOF cat <<EOF >C.json [ { "A": "a0", "C": "c0" }, { "A": "a2", "C": "c2", "X": "x2c" }, { "A": "a4", "C": "c4" }, { "A": "a8", "C": "c6" }, { "A": "a6", "C": "c8" } ] EOF jq --slurp --sort-keys -c 'flatten(1) | group_by(.A) | map(reduce .[] as $x ({}; . * $x))' B.json C.json jq --slurp --sort-keys -c 'flatten(1) | group_by(.A) | map(reduce .[] as $x ({}; . * $x))' C.json B.json
Notez que pour obtenir un rĂ©sultat plus prĂ©dictible lâoption
--sort-keys
a été ajoutée et que pour améliorer la lisibilité les résultats ont été reformatés.Lorsque le fichier B.json est en premier et le fichier C.json en second, on obtient ceci :
[ {"A":"a0","B":"b0","C":"c0"}, {"A":"a1","B":"b1","X":"x1b"}, {"A":"a2","B":"b2","C":"c2","X":"x2c"}, {"A":"a3","B":"b3"}, {"A":"a4","C":"c4"}, {"A":"a6","C":"c8"}, {"A":"a8","C":"c6"} ]
Lorsque on met dâabord le fichier C.json puis le fichier B.json, on obtient cela :
[ {"A":"a0","B":"b0","C":"c0"}, {"A":"a1","B":"b1","X":"x1b"}, {"A":"a2","B":"b2","C":"c2","X":"x2b"}, {"A":"a3","B":"b3"}, {"A":"a4","C":"c4"}, {"A":"a6","C":"c8"}, {"A":"a8","C":"c6"} ]
On constate quâen cas de conflit des donnĂ©es entre les fichiers ce sera le dernier fichier qui sera pris en compte. Ce qui est parfaitement prĂ©dictible dâaprĂšs le code utilisĂ© pour faire la rĂ©duction :
reduce .[] as $x ({}; . * $x)
Références
Documentation sur les filtres :
኿