将扁平的对象数组转换为嵌套的对象数组

15

原始的JSON数据(扁平表):

[
    {"id":"1","first_name":"Jason","last_name":"Martin","start_date":"1996-07-25","end_date":"2006-07-25","salary":"1234.56","city":"Toronto","description":"Programmer","department":"Finance","active":"1"},
    {"id":"2","first_name":"Alison","last_name":"Mathews","start_date":"1976-03-21","end_date":"1986-02-21","salary":"6661.78","city":"Vancouver","description":"Tester","department":"Finance","active":"1"},
    {"id":"3","first_name":"James","last_name":"Smith","start_date":"1978-12-12","end_date":"1990-03-15","salary":"6544.78","city":"Vancouver","description":"Tester","department":"QA","active":"1"},
    {"id":"4","first_name":"Celia","last_name":"Rice","start_date":"1982-10-24","end_date":"1999-04-21","salary":"2344.78","city":"Vancouver","description":"Manager","department":"HR","active":"1"},
    {"id":"5","first_name":"Robert","last_name":"Black","start_date":"1984-01-15","end_date":"1998-08-08","salary":"2334.78","city":"Vancouver","description":"Tester","department":"IT","active":"1"},
    {"id":"6","first_name":"Linda","last_name":"Green","start_date":"1987-07-30","end_date":"1996-01-04","salary":"4322.78","city":"New York","description":"Tester","department":"QA","active":"1"},
    {"id":"7","first_name":"David","last_name":"Larry","start_date":"1990-12-31","end_date":"1998-02-12","salary":"7897.78","city":"New York","description":"Manager","department":"HR","active":"1"}
]

我需要以这种方式调用函数:

nest(data,["city","description","department"])
第一个参数是整个数据集,第二个参数是定义嵌套级别的列的数组。
预期的JSON输出:
[
{key: "city", value: "Toronto", count: 1, children:
    [
        {key: "description", value: "Programmer", count: 1, children:
            [
                {key: "department", value: "Finance", count: 1}
            ]
        }
    ]
},
{key: "city", value: "Vancouver", count: 2, children: 
    [
        {key: "description", value: "Tester", count: 3, children:
            [
                {key: "department", value: "Finance", count: 1},
                {key: "department", value: "QA", count: 1},
                {key: "department", value: "IT", count: 1}
            ]
        },
        {key: "description", value: "Manager", count: 1}
    ]
},

{key: "city", value: "New York", count: 2, children:
    [
        {key: "description", value: "Tester", count: 1, children:
            [
                {key: "department", value: "QA", count: 1}
            ]
        },
        {key: "description", value: "Manager", count: 1, children:
            [
                {key: "department", value: "HR", count: 1}
            ]
        }
    ]
}

我尝试编写了几个递归函数,但是当我必须动态搜索树以避免重复时总是卡住了。


1
为什么使用{key: "department", value: "Finance", count: 1}而不是{key: "department", value: "Finance", count: 0}?因为该节点没有子节点。 - rob mayoff
欢迎来到[SO],请阅读[faq]。 - zzzzBov
什么是计数?即使子元素为零,它也是1吗? - goat
@esskar - 我目前有一个递归函数,但它不起作用,因为我需要一种类似于xpath语法的方式来检查树中是否已经存在一个元素。 - WeaponX86
@zzzzBov - 不好意思,如果这个问题看起来有点复杂,我可以贴出我写的错误代码。 - WeaponX86
4个回答

12

认为这是一个有趣的小问题,所以我做了它,但是我同意那些问“你到目前为止尝试了什么”的人。通常,你应该谈论一个具体的问题。

// Groups a flat array into a tree. 
// "data" is the flat array.
// "keys" is an array of properties to group on.
function groupBy(data, keys) { 

    if (keys.length == 0) return data;

    // The current key to perform the grouping on:
    var key = keys[0];

    // Loop through the data and construct buckets for
    // all of the unique keys:
    var groups = {};
    for (var i = 0; i < data.length; i++)
    {
        var row = data[i];
        var groupValue = row[key];

        if (groups[groupValue] == undefined)
        {
            groups[groupValue] = new Array();
        }

        groups[groupValue].push(row);
    }

    // Remove the first element from the groups array:
    keys.reverse();
    keys.pop()
    keys.reverse();

    // If there are no more keys left, we're done:
    if (keys.length == 0) return groups;

    // Otherwise, handle further groupings:
    for (var group in groups)
    {
        groups[group] = groupBy(groups[group], keys.slice());
    }

    return groups;
}

按照以下方式调用该方法:

var groupedData = groupBy(data, ["city","description","department"]);
这个方法对于你的数据的输出看起来像这样:
{
    "Toronto": {
        "Programmer": {
            "Finance": [
                {
                    "id": "1", "first_name": "Jason", "last_name": "Martin", "start_date": "1996-07-25", "end_date": "2006-07-25", "salary": "1234.56", "city": "Toronto", "description": "Programmer", "department": "Finance", "active": "1"
                }
            ]
        }
    },
    "Vancouver": {
        "Tester": {
            "Finance": [
                {
                    "id": "2", "first_name": "Alison", "last_name": "Mathews", "start_date": "1976-03-21", "end_date": "1986-02-21", "salary": "6661.78", "city": "Vancouver", "description": "Tester", "department": "Finance", "active": "1"
                }
            ],
            "QA": [
                {
                    "id": "3", "first_name": "James", "last_name": "Smith", "start_date": "1978-12-12", "end_date": "1990-03-15", "salary": "6544.78", "city": "Vancouver", "description": "Tester", "department": "QA", "active": "1"
                }
            ],
            "IT": [
                {
                    "id": "5", "first_name": "Robert",  "last_name": "Black", "start_date": "1984-01-15", "end_date": "1998-08-08", "salary": "2334.78", "city": "Vancouver", "description": "Tester", "department": "IT", "active": "1"
                }
            ]
        },
        "Manager": {
            "HR": [
                {
                    "id": "4", "first_name": "Celia", "last_name": "Rice", "start_date": "1982-10-24", "end_date": "1999-04-21", "salary": "2344.78", "city": "Vancouver", "description": "Manager", "department": "HR", "active": "1"
                }
            ]
        }
    },
    "New York": {
        "Tester": {
            "QA": [
                {
                    "id": "6", "first_name": "Linda", "last_name": "Green", "start_date": "1987-07-30", "end_date": "1996-01-04", "salary": "4322.78", "city": "New York", "description": "Tester", "department": "QA", "active": "1"
                }
            ]
        },
        "Manager": {
            "HR": [
                {
                    "id": "7", "first_name": "David", "last_name": "Larry", "start_date": "1990-12-31", "end_date": "1998-02-12", "salary": "7897.78", "city": "New York", "description": "Manager", "department": "HR", "active": "1"
                }
            ]
        }
    }
}
因为这些组都是JavaScript对象,所以不需要 "count" 成员。你可以直接使用数组的 .length 属性。 使用 JavaScript 的 for (var group in groups) 语法循环遍历这些组。

一个不错的解决方案,可以保存以备后用。 - Oisin Lavery
感谢您的精彩文章。我有一个类似的问题,将尝试使用您的方法。请查看我的帖子:https://stackoverflow.com/questions/63878287/how-to-create-a-nested-array-of-objects-from-flat-array-of-objects - jroyce

8
你可以查看 D3.js 中的 nest() 操作符:https://github.com/mbostock/d3/blob/48ad44fdeef32b518c6271bb99a9aed376c1a1d6/src/arrays/nest.js。这是 D3 库的一部分,但快速查看我刚刚提供的代码,我认为它没有任何依赖项,所以您应该能够将此代码用于自己的项目中。使用方法在文档中这里描述 - 你可以使用.key()方法链接定义嵌套结构中每个层次结构的键。在你的情况下,可能会像这样:
data = d3.nest()
    .key(function(d) { return d.city })
    .key(function(d) { return d.description })
    .entries(data);

这个结构与您拥有的略有不同,但在功能上非常相似。
[
  {
    "key": "Toronto", 
    "values": [
      {
        "key": "Programmer", 
        "values": [
          {
            "active": "1", 
            "city": "Toronto", 
            "department": "Finance", 
            "description": "Programmer", 
            "end_date": "2006-07-25", 
            "first_name": "Jason", 
            "id": "1", 
            "last_name": "Martin", 
            "salary": "1234.56", 
            "start_date": "1996-07-25"
          },
          // etc ...
        ]
      }
    ]
  },
  // etc ...
]

不错...相比我草率编写的版本,它有几个优点,包括:开发人员可选择的键(这意味着很容易使键不区分大小写或计算),以及排序。就我个人而言,我可能会调整我的版本以添加这些功能,而不是引入(另一个)库...但是很好的发现! - Steve
1
谢谢 - 你不需要整个库,只需要我链接的代码中的函数,它没有任何D3依赖。但是对于某些项目来说可能过于复杂了。 - nrabinowitz
我一定会看看这个,不错的发现! - WeaponX86
太棒了,这正是我所需要的。绝对是最干净的方法。 - parliament

0

0

在@nrabinowitz提供的示例基础上,这里是使用最初提出的API将集合和属性名称数组作为参数传递的嵌套函数,底层使用d3.nest:

function nest(data, keys) {
  var nest = d3.nest();
  keys.forEach(function(k) { 
    nest.key(function(d) {
      return d[k];
    })
  });
  return nest.entries(data);
}

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