jsonjq

Merge two JSON files with jq, adding integers


I have two JSON files of the form:

{
    "xFruits": {
        "views": 163,
        "all": {
            "xApples": {
                "views": 28,
                "eats": 10,
                "discards": 17
            }
        }
    },
    "xVegetables": {
        "views": 134,
        "all": {
            "xBeans": {
                "views": 29,
                "eats": 11,
                "discards": 14
            },
            "xCarrots": {
                "views": 30,
                "eats": 12,
                "discards": 11
            },
        }
    },
}

Keys starting with x change names and vary in number. The others are fixed. Most x keys exist in both files, but some may exist only in file 1 and others only in file 2.

I want to use jq to merge both files and add numbers when have the same path. So if this is the second file:

{
    "xFruits": {
        "views": 16,
        "all": {
            "xApples": {
                "views": 2,
                "eats": 2,
                "discards": 5
            }
        }
    },
    "xVegetables": {
        "views": 147,
        "all": {
            "xBeans": {
                "views": 12,
                "eats": 7,
                "discards": 13
            },
            "xSpinach": {
                "views": 30,
                "eats": 12,
                "discards": 11
            },
        }
    },
}

The the mix of both would result in:

{
    "xFruits": {
        "views": 179,
        "all": {
            "xApples": {
                "views": 30,
                "eats": 12,
                "discards": 22
            }
        }
    },
    "xVegetables": {
        "views": 281,
        "all": {
            "xBeans": {
                "views": 41,
                "eats": 18,
                "discards": 27
            },
            "xCarrots": {
                "views": 30,
                "eats": 12,
                "discards": 11
            },
            "xSpinach": {
                "views": 30,
                "eats": 12,
                "discards": 11
            },
        }
    },
}

Note how xCarrots and xSpinach retained their original values because each only existed in one file, while all other values were added together.

Is this something jq is capable of?


Solution

  • You could iterate over a stream of path-value arrays using tostream and reduce, and successively build up the result object using getpath, setpath and simple addition. Initially non-existent paths evaluate to null, which for the addition acts as 0.

    jq -n '
      reduce (inputs | tostream | select(has(1))) as [$p,$v]
        (null; setpath($p; getpath($p) + $v))
    ' file1.json file2.json
    
    {
      "xFruits": {
        "views": 179,
        "all": {
          "xApples": {
            "views": 30,
            "eats": 12,
            "discards": 22
          }
        }
      },
      "xVegetables": {
        "views": 281,
        "all": {
          "xBeans": {
            "views": 41,
            "eats": 18,
            "discards": 27
          },
          "xCarrots": {
            "views": 30,
            "eats": 12,
            "discards": 11
          },
          "xSpinach": {
            "views": 30,
            "eats": 12,
            "discards": 11
          }
        }
      }
    }
    

    Demo