将具有相同结构的JSON文件合并到包含列表的JSON文件中

时间:2017-06-24 14:49:39

标签: json list merge jq

我有一些JSON文件,所有文件都具有相同的结构(各处的密钥相同,某些密钥的相应值可能不同)。我想将与某些键相关联的值收集到列表中,并将这些列表存储为与新JSON文件中的这些键关联的值。

例如,考虑这三个文件,我对密钥number_items和相应的值感兴趣。第一个文件 -

[
  {
    "box_id": 1,
    "number_items": 4
  },
  {
    "box_id": 3,
    "number_items": 15
  },
  {
    "box_id": 6,
    "number_items": 2
  }
]

第二档 -

[
  {
    "box_id": 1,
    "number_items": 7
  },
  {
    "box_id": 3,
    "number_items": 15
  },
  {
    "box_id": 6,
    "number_items": 4
  }
]

第三档 -

[
  {
    "box_id": 1,
    "number_items": 5
  },
  {
    "box_id": 3,
    "number_items": 9
  },
  {
    "box_id": 6,
    "number_items": 0
  }
]

这些应该合并成一个看起来像这样的东西 -

[
  {
    "box_id": 1,
    "number_items": [
      4,
      7,
      5
    ]
  },
  {
    "box_id": 3,
    "number_items": [
      15,
      15,
      9
    ]
  },
  {
    "box_id": 6,
    "number_items": [
      2,
      4,
      0
    ]
  }
]

可以使用jq完成吗?如果没有,那么这样做的好方法是什么?请注意,实际场景包含150多个文件,其中包含3个键,其值我想合并到列表中。

3 个答案:

答案 0 :(得分:2)

您可以通过简单地将它们全部作为输入传递来合并具有相似结构的文件。他们的内容将按照他们的顺序进行流式传输。

然后您可以将它们读入单个数组,按box_id对对象进行分组,然后将结果映射出来。

$ jq -n '
    [inputs[]] | group_by(.box_id)
        | map({box_id:.[0].box_id, number_items:map(.number_items)})
' input{1,2,3}.json

产生

[
  {
    "box_id": 1,
    "number_items": [
      4,
      7,
      5
    ]
  },
  {
    "box_id": 3,
    "number_items": [
      15,
      15,
      9
    ]
  },
  {
    "box_id": 6,
    "number_items": [
      4,
      2,
      0
    ]
  }
]

在某些平台上对项目进行分组时,似乎不会保留订单。就我而言,在Windows 64位版本上运行会产生这种情况。如果您想使用group_by,请注意这一点。如果您想避免使用此过滤器,当然还有其他方法,但使用起来会更方便。

答案 1 :(得分:1)

  

我想收集与某些键相关的值

这是一种解决方案,它以相同的方式处理除分组键之外的所有键。它还可以优雅地处理丢失的键,并且不依赖于jq的sort的稳定性。该解决方案基于通用过滤器merge/0,定义如下:

# Combine an array of objects into a single object, ans, with array-valued keys,
# such that for every key, k, in the i-th object of the input array, a,
# ans[k][i] = a[i][k]
# null is used as padding if a value is missing.
# Example:
# [{a:1, b:2}, {b:3, c:4}] | merge
# produces:
# {"a":[1,null],"b":[2,3],"c":[null,4]}
def merge:
  def allkeys: map(keys) | add | unique;
  allkeys as $allkeys
  | reduce .[] as $in ({};
     reduce $allkeys[] as $k (.;
      . + {($k): (.[$k] + [$in[$k]]) } ));

然后可以将给定问题的解决方案表述为:

transpose | map(merge) | map( .box_id |= .[0] )

调用:

  jq -s -f merge.jq input{1,2,3}.json

输出:如问题所示。

更强大的解决方案

上述解决方案假设每个文件中box_id的排序一致。 OP要求似乎证明了这一假设,但为了安全性和稳健性,首先要对对象进行排序:

map(sort_by(.box_id)) | transpose | map( merge | (.box_id |= .[0]) )

请注意,这仍假设任何输入文件中都没有box_id的缺失值。

更强大的解决方案

如果任何输入文件中可能缺少某些box_id值,则添加缺失值是合适的。这可以通过以下过滤器完成:

# Input: a matrix of objects (that is, an array of rows of objects),
#   each of which is assumed to have a distinguished field, f,
#   with distinct values on each row;
# Output: a rectangular matrix such that every row, r, of the output
#   matrix includes the elements of the corresponding row of the input
#   matrix, with additional elements as necessary so that (r |
#   map(.id) | sort) is the same for all rows r.
#
def rectanglize(f):
  def ids: [.[][] | f] | unique;
  def it: . as $in | {} | (f = $in);
  ids as $ids
  | map( . + ( $ids - [.[]|f] | map(it) ) )
;  

将所有内容放在一起,主要管道变为:

rectanglize(.id)
| map(sort_by(.box_id))
| transpose 
| map( merge | .box_id |= .[0] )

答案 2 :(得分:0)

根据您尝试保存此新文件(本地与服务器)的位置,有几种不同的方法。据我所知,没有可能的方法在本地保存文件而不使用其中一个可用的插件(How to write data to a JSON file using Javascript)。如果要将其保存到服务器,使用JavaScript是不可能的,最好使用背景语言。

这是一种将多个JSON文件的内容组合成所需格式的方法。

// send json files you want combined, and a new file path and name (path/to/filename.json)
  function combineJsonFiles(files, newFileName) {
    var combinedJson = [];
    // iterate through each file 
    $.each(files, function(key, fileName) {
      // load json file
      // wait to combine until loaded. without this 'when().done()', boxes would return 'undefined'
      $.when(loadJsonFile(fileName)).done(function(boxes) {
        // combine json from file with combinedJson array
        combinedJson = combineJson(boxes, combinedJson);
        // check if this is the last file
        if (key == files.length-1) {
          // puts into json format
          combinedJson = JSON.stringify(combinedJson);
          // your json is now ready to be saved to a file
        }
      });
    });
  }

  function loadJsonFile(fileName) {
    return $.getJSON(fileName);
  }



function combineJson(boxes, combinedJson) {
  // iterate through each box 
  $.each(boxes, function(key, box) {
    // use grep to search if this box's id is already included
    var matches = $.grep(combinedJson, function(e) { return e.box_id == box.box_id; });

    // if there are no matches, add box to the combined file
    if (matches.length == 0) {

      var newBox = { box_id: box.box_id };

      // iterate through properties of box
      for (var property in box) {
        // check to ensure that properties are not inherited from base class
        if (box.hasOwnProperty(property)) {
          // will ignore if property is box_id
          if (property !== 'box_id') {
            // box is reformatted to make the property type into array
            newBox[property] = [box[property]];
          }
        }
      }
      combinedJson.push(newBox);
    } else {
      // select first match (there should never be more than one)
      var match = matches[0];

      // iterate through properties of box
      for (var property in box) {
        // check to ensure that properties are not inherited from base class
        if (box.hasOwnProperty(property)) {
          // will ignore if property is box_id
          if (property !== 'box_id') {
            // add property to the already existing box in the combined file
            match[property].push(box[property]);
          }
        }
      }
    }
  });
  return combinedJson;
}

  var jsonFiles = ['path/to/data.json', 'path/to/data2.json', 'path/to/data3.json'];

  combineJsonFiles(jsonFiles, 'combined_json.json');

这个的JSON输出如下:

[{"box_id":1,"number_items":[4,7,5]},{"box_id":3,"number_items":[15,15,9]},{"box_id":6,"number_items":[2,4,0]}]

希望这有帮助!