在JavaScript中将数组转换为嵌套对象

5
我拥有典型的组织层级结构。例如:

D,E is reporting to B. B,C is reporting to A.

A是最顶层的节点。但我收到的数据是一个扁平数组,其中包含一个指向父节点的属性。

    [{
      name: "A",
      parent: null
    },
    {
      name: "B",
      parent: "A"
    },
    {
      name: "C",
      parent: "A"
    },
    {
      name: "D",
      parent: "B"
    },
    {
      name: "E",
      parent: "B"
    }]

但我希望将其转换为单个嵌套对象树形结构。根节点具有嵌入的子节点属性,每个子节点都有自己的子节点属性,如下所示。
    {
      name: "A",
      children: [{
        name: "C"
        children: [{
          name: "D"
        },{
          name: "E"
        }]
      },{
        name: "C"
      }]
    }

如何在JavaScript中高效地实现此功能?
3个回答

5
与其他解决方案不同,此解决方案使用单个循环-数据的顺序不重要-示例与问题的顺序不同。
var peeps = [
    { name: "D", parent: "B" }, 
    { name: "B", parent: "A" },
    { name: "A", parent: null }, 
    { name: "C", parent: "A" }, 
    { name: "E", parent: "B" }
];

var tree;
var obj = {};
peeps.forEach(function (peep) {
    var name = peep.name,
        parent = peep.parent,
        a = obj[name] || { name: name };
    if (parent) {
        obj[parent] = obj[parent] || { name: parent };
        obj[parent].children = obj[parent].children || [];
        obj[parent].children.push(a);
    } else {
        tree = obj[name];
    }
    obj[name] = obj[name] || a;
});
console.log(tree);

这里关于 a = obj[name] || { name: name }; 的一个小注释,你可以使用 peep 替换新的对象,并将 a 替换为 obj[name],完整的 obj[name] = obj[name] || peep;,在 a 使用 obj[name] 之后,现在可以删除最后一行 obj[name] = obj[name] || a; - Nina Scholz
@NinaScholz 不太确定。你试过了吗?首先,输出格式不包括“parent”属性。此外,代码中的表面疯狂背后有一种方法,允许输入数据以任意顺序出现。 - Jaromanda X
它可以工作,请查看我的回答。关于“parent”属性,你是正确的,我只包含了所有属性。(op没有要求它们)。 - Nina Scholz

4

这个方案采用了Jaromanda X'的解决方案并进行了一些添加。

  1. 使用Array.prototype.reduce代替Array.prototype.forEach,因为需要一个临时变量和返回值。

  2. 保留r [a.name] .children的内容,并将其分配给a.children

  3. 节点a被分配给r [a.name]。 因此,节点对象的所有属性都保留下来,例如prop1 ...prop5

  4. 根节点被分配给r._以便后续使用。

var data = [
        { name: "D", parent: "B", prop1: 'prop1' },
        { name: "B", parent: "A", prop2: 'prop2' },
        { name: "A", parent: null, prop3: 'prop3' },
        { name: "C", parent: "A", prop4: 'prop4' },
        { name: "E", parent: "B", prop5: 'prop5' }
    ],
    tree = data.reduce(function (r, a) {
        a.children = r[a.name] && r[a.name].children;
        r[a.name] = a;
        if (a.parent) {
            r[a.parent] = r[a.parent] || {};
            r[a.parent].children = r[a.parent].children || [];
            r[a.parent].children.push(a);
        } else {
            r._ = a;
        }
        return r;
    }, {})._;
document.write('<pre>' + JSON.stringify(tree, 0, 4) + '</pre>');

如果有多个根节点,您可以使用根节点的父级作为访问器来获取子节点并返回该数组。

var data = [{ name: "D", parent: "B", prop1: 'prop1' }, { name: "B", parent: "A", prop2: 'prop2' }, { name: "A", parent: null, prop3: 'prop3' }, { name: "C", parent: "A", prop4: 'prop4' }, { name: "E", parent: "B", prop5: 'prop5' }, { name: "A1", parent: null, prop3: 'prop3' }],
    tree = data.reduce(function (r, a) {
        if (r[a.name] && r[a.name].children) { // prevent empty children array
            a.children = r[a.name].children;
        }
        r[a.name] = a;
        r[a.parent] = r[a.parent] || {};
        r[a.parent].children = r[a.parent].children || [];
        r[a.parent].children.push(a);
        return r;
    }, {}).null.children; // take root value as property accessor

console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }


1
我有相似的代码,使用reduce,并在reduction变量上设置了一个“特殊”的属性,甚至将其称为_ - 我认为这可能会让OP有点不知所措 - 但是我仍然使用我的逻辑返回了所请求的确切结构 :p - Jaromanda X
抱歉打扰了,但是如果有一些根评论而不仅仅是一个,其中父级为Null,则您两个代码都无法正常工作。 - DenimTornado
@DenimTornado,请查看第2部分,其中包含多个根元素。 - Nina Scholz
@NinaScholz 很酷,它能工作,只有一点,你能澄清一下最后的“.null.children;”部分吗? - DenimTornado
r[a.parent] 创建了一个属性,其键为 'null'(所有键都是字符串,因此将 null 作为字符串),这是已知的根对象符号。这被用来获取其子项。 - Nina Scholz

2
您可以使用while循环来实现此操作:
var data = [
    { name: "A", parent: null },
    { name: "B", parent: "A" },
    { name: "C", parent: "A" },
    { name: "D", parent: "B" },
    { name: "E", parent: "B" }
];

var root = data.find(function(item) {
    return item.parent === null;
});

var tree = {
    name: root.name
};

var parents = [tree];
while (parents.length > 0) {
    var newParents = [];
    parents.forEach(function(parent) {
        var childs = data.filter(function(item) {
            return item.parent == parent.name
        }).forEach(function(child) {
            var c = { name: child.name };
            parent.children = parent.children || [];
            parent.children.push(c);
            newParents.push(c);
        });
    });
    parents = newParents;
}

console.log(tree);

如果提供的数组顺序不正确,这段代码将会失败。例如,给定以下数组:var data = [, { name: "B", parent: "A" }, { name: "A", parent: null }, { name: "C", parent: "A" }, { name: "D", parent: "B" }, { name: "E", parent: "B" } ];以上代码将会出现错误。 - Simon Brahan
1
@SimonBrahan 不会失败的。你忘了在数据中去掉逗号。 - madox2
1
@madox2,应该是console.log(tree),对吗?你能解释一下这是怎么工作的吗?你没有在任何地方修改tree对象,但结果却在tree中。我很困惑。 - espeirasbora
@espeirasbora,你是正确的,那有点打错了。我批准了编辑修复它。 - madox2

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