jsonjqarray-mergefilemerge

Combining json files using jq objects with array


I've tried using

jq "reduce inputs.skins as $s (.; .skins += $s)" file1.json file2.json > combined.json

but it just creates two boots.name and fun.name from each file

any way I can use jq and combine the objects and arrays without having duplicates?

I apologize for any confusion, jq is kind of complicated to find an easy tutorial for me to understand

file1.json

{
  "skins": [
    {
      "Item Shortname": "boots.name",
      "skins": [
        2,
        25,
        41,
      ]
    },
    {
      "Item Shortname": "fun.name",
      "skins": [
        12,
        8,
      ]
    }
   ]
}

file2.json

{
  "skins": [
    {
      "Item Shortname": "boots.name",
      "skins": [
        2,
        20,

      ]
    },
    {
      "Item Shortname": "fun.name",
      "skins": [
        90,
        6,
        82,
      ]
    }
   ]
}

combined.json

{
  "skins": [
    {
      "Item Shortname": "boots.name",
      "skins": [
        2,
        20,
        25,
        41,
      ]
    },
    {
      "Item Shortname": "fun.name",
      "skins": [
        90,
        6,
        82,
        12,
        8,
      ]
    }
   ]
}

Solution

  • The tricky part here is meeting the apparent uniqueness requirements, for which the following generic filter can be used:

    # emit a stream of the distinct items in `stream`
    def uniques(stream):
     foreach stream as $s ({};
       ($s|type) as $t
       | (if $t == "string" then $s else ($s|tostring) end) as $y
       | if .[$t][$y] then .emit = false else .emit = true | (.item = $s) | (.[$t][$y] = true) end;
       if .emit then .item else empty end );
    

    This ensures the ordering is preserved. It's a bit tricky because it is completely generic -- it allows both 1 and "1" and distinguishes between them, just as unique does.

    (If the ordering did not matter, then you could use unique.)

    So, assuming an invocation along the lines of

    jq -s -f program.jq file1.json file2.json
    

    you would place the above def followed by the following “main” program in program.jq:

      .[0] as $file1 | .[1] as $file2
      | (INDEX($file1.skins[]; .["Item Shortname"]) | map_values(.skins)) as $dict
    
      | $file2
      | .skins |= map( .["Item Shortname"] as $name
                       | .skins += $dict[$name]
                       | .skins |= [uniques(.[])] )
    

    A better solution would avoid the -s option (e.g. as shown below), but the above method of feeding the two files to jq is at least straightforward, and will work regardless of which version of jq you are using.

    Solution using input

    One way to avoid slurping the two files would be to use input in conjunction with the -n command line option instead of -s. The "main" part of the jq program would then be as follows:

    (INDEX(input.skins[]; .["Item Shortname"]) | map_values(.skins)) as $dict
    | input
    | .skins |= map( .["Item Shortname"] as $name
                     | .skins += $dict[$name]
                     | .skins |= [uniques(.[])] )