在node.js中按多个字段对JSON进行分组

3
我有一个如下的JSON格式。

var test = [{

   "id": "3",
   "city": "seattle",
   "place" : "xxx",
   "usage" : "163612",
   "available": "162500"

}, {

   "id": "4",
   "city": "washington",
   "place" : "xxx",
   "usage" : "52542",
   "available": "86624"

}, {

   "id": "3",
   "city": "seattle",
   "place" : "yyy",
   "usage" : "163612",
   "available": "962500"

},
{

   "id": "5",
   "city": "seattle",
   "place" : "yyy",
   "usage" : "562",
   "available": "24252"
},
{

   "id": "4",
   "city": "washington",
   "place" : "yyy",
   "usage" : "163612",
   "available": "319250"

}]

我希望将此JSON按'id'和'city'进行分组。新形成的分组JSON应如下所示。

[
    {
        "3": {
            "seattle": [
                {
                    "xxx": {
                        "usage": "163612",
                        "available": "162500"
                    }
                },
                {
                    "yyy": {
                        "usage": "163612",
                        "available": "962500"
                    }
                }
            ]
        }
    },
    {
        "4": {
            "washington": [
                {
                    "xxx": {
                        "usage": "52542",
                        "available": "86624"
                    }
                },
                {
                    "yyy": {
                        "usage": "163612",
                        "available": "319250"
                    }
                }
            ]
        }
    },
    {
        "5": {
            "seattle": [
                {
                    "xxx": {
                        "usage": "562",
                        "available": "24252"
                    }
                }
            ]
        }
    }
]

我尝试使用循环和排序,但无法获得所需的结果。是否有任何方法可以构造这个JSON。


1
这是一个 JavaScript 对象,而不是 JSON。 - Amberlamps
3个回答

2
这里是一种按多个字段分组的方法:

http://jsbin.com/xixemo/edit?js,console

(function () {
  "use strict";

  var test = [{

   "id": "3",
   "city": "seattle",
   "place" : "xxx",
   "usage" : "163612",
   "available": "162500"

}, {

   "id": "4",
   "city": "washington",
   "place" : "xxx",
   "usage" : "52542",
   "available": "86624"

}, {

   "id": "3",
   "city": "seattle",
   "place" : "yyy",
   "usage" : "163612",
   "available": "962500"

},
{

   "id": "5",
   "city": "seattle",
   "place" : "yyy",
   "usage" : "562",
   "available": "24252"
},
{

   "id": "4",
   "city": "washington",
   "place" : "yyy",
   "usage" : "163612",
   "available": "319250"

}],
      getRemainingProperties = function (obj, propertiesToExclude) {
        return Object.keys(obj)
          .filter(function (key) {
            return !propertiesToExclude.includes(key);
          })  
          .reduce(function (acc, curr) {
            var result = {};

            if (!acc) {
              result[curr] = obj[curr];
              return result;
            }
            result = acc;
            result[curr] = obj[curr];
            return result;          
          }, undefined);
      },
      excludedProperties = ["id", "city", "place"],
      transformCity = function (cityInformation) {
        var id = {},        
            city = {},
            place = {},
            remainder = getRemainingProperties(cityInformation, excludedProperties);
        place[cityInformation.place] = remainder;
        city[cityInformation.city] = [place];
        id[cityInformation.id] = city;
        return id;
      },
      initialReduceUndefinedValue,
      idExists = function (searchArray, id) {
        return searchArray.reduce(function (acc, curr) {
          if (!acc){
            return curr.hasOwnProperty(id);
          }
          return true;
        }, undefined);
      },
      lift = function (array) {
        //returns an object from inside container array without using array index
        if (!Array.isArray(array)) {
          return array;
        }
        return array.reduce(function (acc, curr) {
          return curr;
        });
      },
      answer = test.reduce(function (acc, curr) {
        var result, 
            matchingId, //create a new object that will have appended properties for the current city
            missingPlace = {};

        if (!acc) {
          return [transformCity(curr)];
        }
        if (idExists(acc, curr.id)) {
          result = acc.filter(function (obj) {
            //store the unmodified objects to return
            return !obj.hasOwnProperty(curr.id);
          });
          matchingId = lift(acc.filter(function (obj) {
            return obj.hasOwnProperty(curr.id);
          }));

          if (!matchingId[curr.id].hasOwnProperty(curr.city)) {
            //if the object does not have the city, then add the city
            matchingId[curr.city] = {};
          }
          if (!matchingId[curr.id][curr.city].hasOwnProperty(curr.place)) {
            //if the object does not have the place, then add the place            
            missingPlace[curr.place] = getRemainingProperties(curr, excludedProperties);
            matchingId[curr.id][curr.city].push(missingPlace);
          }
          result.push(matchingId);//add here just incase a city is duplicated
          return result;
        } else {//unique city id found, add new city
          acc.push(transformCity(curr));
        }
        return acc;
      }, initialReduceUndefinedValue);

  console.log(answer);
}());

我已经将剩余属性的包含进行了概括,这样它们就不必显式地定义(正如评论中所请求的那样)。
我试图避免使用for循环,以便迭代细节从解决方案中提取出来。我还采用了函数式编程方法,并尽量避免创建具有副作用的函数。在加载到jsbin中的解决方案中,我添加了一个数组包含的polyfill,希望它能够进入ECMAScript第7版(预计在2016年发布)。有一个有用的“Javascript中的函数式编程”教程可能会有所帮助:

http://jhusain.github.io/learnrx/

这个答案的一个可能扩展是对分组数据进行排序,以匹配OP列出的示例输出。

1
我不知道那是否是你在寻找的。

 var newObj = [];

 for (var i in test) {
  var cityObj = test[i];

  var newItem = {};

  var foundItem = false;
  for (var j in newObj) {
   var existingItem = newObj[j];
   if (newObj[j].hasOwnProperty(cityObj.id)) {
    foundItem = j;
   }
  }

  if (!foundItem) {
   newItem[cityObj.id] = {};
   newItem[cityObj.id][cityObj.city] = {};
   newItem[cityObj.id][cityObj.city][cityObj.place] = { usage: cityObj.usage, available: cityObj.available };
   newObj.push(newItem);
  } else {
   newObj[foundItem][cityObj.id][cityObj.city][cityObj.place] = { usage: cityObj.usage, available: cityObj.available };
  }
 }

 console.dir(newObj);

请告诉我这是否有帮助。
根据您的描述进行更改:

 var newObj = [];

 for (var i in test) {
  var cityObj = test[i];

  var newItem = {};

  var foundItem = false;
  for (var j in newObj) {
   var existingItem = newObj[j];
   if (newObj[j].hasOwnProperty(cityObj.id)) {
    foundItem = j;
   }
  }

  if (!foundItem) {
   newItem[cityObj.id] = {};
   newItem[cityObj.id][cityObj.city] = [];
   var place = {};
   place[cityObj.place] = { usage: cityObj.usage, available: cityObj.available };
   newItem[cityObj.id][cityObj.city].push(place);
   newObj.push(newItem);
  } else {
   var place = {};
   place[cityObj.place] = { usage: cityObj.usage, available: cityObj.available };
   newObj[foundItem][cityObj.id][cityObj.city].push(place);
  }
 }

 console.dir(newObj);


这非常接近我想要的。但是,分组JSON中的城市部分应该是一个JSON数组。此外,这一行“newItem [cityObj.id] [cityObj.city] [cityObj.place] = {usage:cityObj.usage,available:cityObj.available};”就像在JSON中硬编码剩余的属性。我们能否通过在源JSON中除了id、city和place之外获取所有属性来形成它呢? - NagaLakshmi
@NagaLakshmi 如果你不想硬编码像usage和available这样的属性,你需要硬编码那些你不想包含在被推送到city数组中的对象中的属性,并且在迭代cityObj对象的属性时排除它们。但我觉得这仍然是同样的工作,只是以不同的方式完成。请检查上面更新的答案,如果有任何帮助,请给我回复。 - Tomasz

0

我认为这将是一个更好的解决方案:

function group(data, column) {
  var generatedData = {};
  for (var i in data){
    var dt = data[i];
    var key = dt[column];
    if (!(key in generatedData)) {
      generatedData[key] = [];
    }
    generatedData[key].push(dt);
  }
  return generatedData;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接