Home > front end >  How to merge and aggregate values in 2 JSON files using jq?
How to merge and aggregate values in 2 JSON files using jq?

Time:09-17

I am using jq in a shell script to manipulate JSON files.

I have 2 files and I'd like to merge them into one file while also aggregating (sum) the values when names in the name/value pairs are the same.

As an example:

Input1.json

[
  {
    "A": "Name 1",
    "B": "1.1",
    "C": "2"
  },
  {
    "A": "Name 2",
    "B": "3.2",
    "C": "4"
  }
]

Input2.json

[
  {
    "A": "Name 2",
    "B": "5",
    "C": "6"
  },
  {
    "A": "Name 3",
    "B": "7",
    "C": "8"
  }
]

Expected result:

Output.json

[
  {
    "A": "Name 1",
    "B": "1.1",
    "C": "2"
  },
  {
    "A": "Name 2",
    "B": "8.2",
    "C": "10"
  },
  {
    "A": "Name 3",
    "B": "7",
    "C": "8"
  }
]

I can use other tools other than jq but prefer to ultimately keep the solution contained into a shell script I can call from the Terminal.

Any help is appreciated. Thank you.

CodePudding user response:

I can use other tools other than jq but prefer to ultimately keep the solution contained into a shell script I can call from the Terminal.

You could give the JSON parser a try:

$ xidel -se '
  array{
    let $src:=(json-doc("Input1.json")(),json-doc("Input2.json")())
    for $name in distinct-values($src/A)
    let $obj:=$src[A=$name]
    return
    if (count($obj) gt 1) then
      map:merge(
        $obj[1]() ! {
          .:if ($obj[1](.) castable as decimal) then
            string($obj[1](.)   $obj[2](.))
          else
            $obj[1](.)
        }
      )
    else
      $obj
  }
'

Intermediate steps.

CodePudding user response:

Here's one way, but there are others:

jq -s '
 def to_n: tonumber? // null;
 def merge_values($x;$y): 
   if $x == $y then $x
   elif $x == null then $y
   elif $y == null then $x
   else ($x|to_n) as $xn
   | if $xn then ($y|to_n) as $yn | ($xn $yn)|tostring
     else [$x, $y]
     end
   end;
 def merge($x;$y):
   reduce ($x   $y |keys_unsorted)[] as $k (null;
     .[$k] = merge_values($x[$k]; $y[$k]) );

   

 INDEX(.[0][]; .A) as $in1
 | INDEX(.[1][]; .A) as $in2
 | ($in1   $in2|keys_unsorted) as $keys
 | reduce $keys[] as $k ([];
     .   [merge($in1[$k]; $in2[$k]) ])

    
    
' input1.json inut2.json

CodePudding user response:

jq is beautiful for problems like this:

$ jq -n '
reduce inputs[] as {$A,$B,$C} ({};
    .[$A] |= {
        $A,
        B: (.B   ($B|tonumber)),
        C: (.C   ($C|tonumber))
    }
)
| map({
    A,
    B: (.B|tostring),
    C: (.C|tostring)
  })
' input1.json input2.json

The first reduce creates a map from the different "A" values to the aggregated result object. Then given the mapping, converts back to an array of the result objects adjusting the types of the results.

jqplay

  • Related