cClaude.rocks ☕ Le blog

[Nouvelles technologies du libre, sciences et coups de gueule…]

Menu

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 commande map 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 :

኿


â„č 2006 - 2024 | 🏠 Accueil du domaine | 🏡 Accueil du blog