249 votes

Comment fusionner 2 objets JSON à partir de 2 fichiers en utilisant jq?

J'utilise les outils jq (jq-json-processor) dans un script shell pour analyser du json.

J'ai 2 fichiers json et je veux les fusionner en un seul fichier unique

Voici le contenu des fichiers:

fichier1

{
    "valeur1": 200,
    "horodatage": 1382461861,
    "valeur": {
        "aaa": {
            "valeur1": "v1",
            "valeur2": "v2"
        },
        "bbb": {
            "valeur1": "v1",
            "valeur2": "v2"
        },
        "ccc": {
            "valeur1": "v1",
            "valeur2": "v2"
        }
    }
}

fichier2

{
    "statut": 200,
    "horodatage": 1382461861,
    "valeur": {
        "aaa": {
            "valeur3": "v3",
            "valeur4": 4
        },
        "bbb": {
            "valeur3": "v3"
        },      
        "ddd": {
            "valeur3": "v3",
            "valeur4": 4
        }
    }
}

résultat attendu

{
    "valeur": {
        "aaa": {
            "valeur1": "v1",
            "valeur2": "v2",
            "valeur3": "v3",
            "valeur4": 4
        },
        "bbb": {
            "valeur1": "v1",
            "valeur2": "v2",
            "valeur3": "v3"
        },
        "ccc": {
            "valeur1": "v1",
            "valeur2": "v2"
        },
        "ddd": {
            "valeur3": "v3",
            "valeur4": 4
        }
    }
}

J'ai essayé beaucoup de combinaisons mais le seul résultat que j'obtiens est le suivant, qui n'est pas le résultat attendu:

{
  "ccc": {
    "valeur2": "v2",
    "valeur1": "v1"
  },
  "bbb": {
    "valeur2": "v2",
    "valeur1": "v1"
  },
  "aaa": {
    "valeur2": "v2",
    "valeur1": "v1"
  }
}
{
  "ddd": {
    "valeur4": 4,
    "valeur3": "v3"
  },
  "bbb": {
    "valeur3": "v3"
  },
  "aaa": {
    "valeur4": 4,
    "valeur3": "v3"
  }
}

En utilisant cette commande:

jq -s '.[].valeur' fichier1 fichier2

303voto

Simo Kinnunen Points 4267

Depuis la version 1.4, cela est désormais possible avec l'opérateur *. Lorsqu'il est donné deux objets, il les fusionnera de manière récursive. Par exemple,

jq -s '.[0] * .[1]' file1 file2

Important: Notez le drapeau -s (--slurp), qui place les fichiers dans le même tableau.

Cela vous donnerait :

{
  "value1": 200,
  "timestamp": 1382461861,
  "value": {
    "aaa": {
      "value1": "v1",
      "value2": "v2",
      "value3": "v3",
      "value4": 4
    },
    "bbb": {
      "value1": "v1",
      "value2": "v2",
      "value3": "v3"
    },
    "ccc": {
      "value1": "v1",
      "value2": "v2"
    },
    "ddd": {
      "value3": "v3",
      "value4": 4
    }
  },
  "status": 200
}

Si vous voulez également vous débarrasser des autres clés (comme dans votre résultat attendu), une façon de le faire est la suivante :

jq -s '.[0] * .[1] | {value: .value}' file1 file2

Ou la méthode probablement un peu plus efficace (car elle ne fusionne pas les autres valeurs) :

jq -s '.[0].value * .[1].value | {value: .}' file1 file2

128voto

user2259432 Points 1212

Utilisez jq -s add :

$ echo '{"a":"foo","b":"bar"} {"c":"baz","a":0}' | jq -s add
{
  "a": 0,
  "b": "bar",
  "c": "baz"
}

Cela lit tous les textes JSON de l'entrée standard dans un tableau (jq -s le fait) puis les "réduit".

(add est défini comme def add: reduce .[] as $x (null; . + $x);, qui itère sur les valeurs de l'array/objet d'entrée et les additionne. L'addition d'objets == fusion.)

61voto

jrib Points 711

Voici une version qui fonctionne de manière récursive (en utilisant *) sur un nombre arbitraire d'objets :

echo '{"A": {"a": 1}}' '{"A": {"b": 2}}' '{"B": 3}' |\
  jq --slurp 'reduce .[] as $item ({}; . * $item)'

{
  "A": {
    "a": 1,
    "b": 2
  },
  "B": 3
}

48voto

FiloSottile Points 322

Qui sait si vous en avez encore besoin, mais voici la solution.

Une fois que vous arrivez à l'option --slurp, c'est facile !

--slurp/-s:
    Au lieu d'exécuter le filtre pour chaque objet JSON dans l'entrée,
    lire le flux d'entrée entier dans un grand tableau et exécuter le filtre une seule fois.

Ensuite, l'opérateur + fera ce que vous voulez :

jq -s '.[0] + .[1]' config.json config-user.json

(Remarque : si vous souhaitez fusionner les objets internes au lieu de simplement écraser les objets du fichier de gauche par ceux du fichier de droite, vous devrez le faire manuellement)

26voto

pmf Points 459

Aucune solution ou commentaire donné jusqu'à présent ne considère l'utilisation de input pour accéder au deuxième fichier. Son utilisation rendrait inutile la construction d'une structure supplémentaire à extraire, telle que l'array tout-en-un lors de l'utilisation de l'option --slurp (ou -s), qui figure dans presque toutes les autres approches.

Pour fusionner deux fichiers au niveau supérieur, ajoutez simplement le second fichier depuis input au premier dans . en utilisant + :

jq '. + input' file1.json file2.json

Pour fusionner deux fichiers de manière récursive sur tous les niveaux, faites la même chose en utilisant * en tant qu'opérateur :

jq '. * input' file1.json file2.json

Ceci dit, pour fusionner de manière récursive vos deux fichiers, avec les deux objets réduits à leur champ valeur, filtrez-les d'abord en utilisant {valeur} :

jq '{valeur} * (input | {valeur})' file1.json file2.json

{
  "valeur": {
    "aaa": {
      "valeur1": "v1",
      "valeur2": "v2",
      "valeur3": "v3",
      "valeur4": 4
    },
    "bbb": {
      "valeur1": "v1",
      "valeur2": "v2",
      "valeur3": "v3"
    },
    "ccc": {
      "valeur1": "v1",
      "valeur2": "v2"
    },
    "ddd": {
      "valeur3": "v3",
      "valeur4": 4
    }
  }
}

Démo

Remarquez qu'une solution qui réduit seulement après la fusion, comme . * input | {valeur} serait plus courte en code mais ressuscite l'inutilité de la "construction d'une structure supplémentaire à extraire", ce qui peut produire beaucoup de surcharge si les parties finalement coupées deviennent grandes.

Pour opérer sur plus de deux fichiers, utilisez plusieurs fois input ou itérez de manière programmable sur tous en utilisant plutôt inputs, comme ceci :

jq 'reduce inputs as $i (.; . * $i)' file*.json

Remarquez que dans les deux cas le premier fichier est toujours accédé via le contexte d'entrée . tandis que input(s) adresse uniquement les fichiers restants, c'est-à-dire à partir du deuxième (à moins bien sûr que l'option --null-input ou -n soit donnée).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X