【问题标题】:Group array of nested objects with multiple levels in JavaScript在 JavaScript 中对具有多个级别的嵌套对象进行分组
【发布时间】:2020-05-14 04:29:24
【问题描述】:

我正在尝试将具有多个属性的大型嵌套对象分组,例如:

[
  {
    "id": 14,
    "name": "Name14",
    "theme": true,
    "sub": {
      "id": 70,
      "name": "Name70"
    }
  },
  {
    "id": 14,
    "name": "Name14",
    "theme": true,
    "sub": {
      "id": 61,
      "name": "Name61"
    }
  },
  {
    "id": 14,
    "name": "Name14",
    "theme": true,
    "sub": {
      "id": 4,
      "name": "Name4",
      "sub": {
        "id": 5,
        "name": "Name5",
        "sub": {
          "id": 29,
          "name": "Name29"
        }
      }
    }
  },
  {
    "id": 14,
    "name": "Name14",
    "theme": true,
    "sub": {
      "id": 4,
      "name": "Name4",
      "sub": {
        "id": 5,
        "name": "Name5",
        "sub": {
          "id": 8,
          "name": "Name8",
          "sub": {
            "id": 163,
            "name": "Name163"
          }
        }
      }
    }
  },
  {
    "id": 10,
    "name": "Name10",
    "sub": {
      "id": 4,
      "name": "Name4"
    }
  }
]

如您所见,“sub”目前还不是数组,但即使其中只有一个对象,它们也会出现在预期的输出中。
我想递归地按对象的 id 对数组进行分组以获得这种输出:

[
  {
    "id": 14,
    "name": "Name14",
    "theme": true,
    "sub": [
      {
        "id": 70,
        "name": "Name70"
      },
      {
        "id": 61,
        "name": "Name61"
      },
      {
        "id": 4,
        "name": "Name4",
        "sub": [
          {
            "id": 5,
            "name": "Name5",
            "sub": [
              {
                "id": 29,
                "name": "Name29"
              },
              {
                "id": 8,
                "name": "Name8",
                "sub": [
                  {
                    "id": 163,
                    "name": "Name163"
                  }
                ]
              }
            ]
          }
        ]
      }
    ]
  },
  {
    "id": 10,
    "name": "Name10",
    "sub": [
      {
        "id": 4,
        "name": "Name4"
      }
    ]
  }
]

到目前为止,我用 lodash 和 d3.nest() 尝试了一些恶作剧,但我似乎无法对其进行分组。
大家有没有遇到过类似的情况?如果是这样,您是如何编写代码的?

非常感谢

【问题讨论】:

    标签: javascript arrays node.js group-by nested


    【解决方案1】:

    您可以使用递归方法,通过查找相同的id 将对象合并到数组中。

    const
        merge = (target, { sub, ...o }) => {
            let temp = target.find(({ id }) => id === o.id);
            if (sub) sub = merge(temp?.sub || [], sub)
            if (!temp) target.push(temp = { ...o, sub });
            return target;
        };
    
    var data = [{ id: 14, name: "Name14", theme: true, sub: { id: 70, name: "Name70" } }, { id: 14, name: "Name14", theme: true, sub: { id: 61, name: "Name61" } }, { id: 14, name: "Name14", theme: true, sub: { id: 4, name: "Name4", sub: { id: 5, name: "Name5", sub: { id: 29, name: "Name29" } } } }, { id: 14, name: "Name14", theme: true, sub: { id: 4, name: "Name4", sub: { id: 5, name: "Name5", sub: { id: 8, name: "Name8", sub: { id: 163, name: "Name163" } } } } }, { id: 10, name: "Name10", sub: { id: 4, name: "Name4" } }],
        result = data.reduce(merge, []);
    
    console.log(result);
    .as-console-wrapper { max-height: 100% !important; top: 0; }

    【讨论】:

    • 不错!与我的方法相同(递归),但更智能:)
    • 非常感谢尼娜!这正是我想要的,你的代码看起来很棒!
    【解决方案2】:

    您可以为每个sub 属性创建一个Map——由id 键入——并将对象合并到这些映射中。然后最后将那些sub-maps 转换回sub-arrays:

    function mergeArray(arr) {
        function mergeObject(a, b) {
            while (b = b.sub) {
                if (!a.sub) a.sub = new Map;
                let child = a.sub.get(b.id);
                if (child) a = child;
                else a.sub.set(b.id, a = { id: b.id, name: b.name });
            }
        }
    
        function convertMap(map) {
            return Array.from(map.values(), obj => {
                if (obj.sub) obj.sub = convertMap(obj.sub);
                return obj;
            });
        }
    
        let map = new Map(arr.map(({id, name}) => [id, {id, name}]));
        for (let item of arr) mergeObject(map.get(item.id), item);
        return convertMap(map);
    }
    
    // Demo with input from question
    let input = [{"id": 14,"name": "Name14","theme": true,"sub": {"id": 70,"name": "Name70"}},{"id": 14,"name": "Name14","theme": true,"sub": {"id": 61,"name": "Name61"}},{"id": 14,"name": "Name14","theme": true,"sub": {"id": 4,"name": "Name4","sub": {"id": 5,"name": "Name5","sub": {"id": 29,"name": "Name29"}}}},{"id": 14,"name": "Name14","theme": true,"sub": {"id": 4,"name": "Name4","sub": {"id": 5,"name": "Name5","sub": {"id": 8,"name": "Name8","sub": {"id": 163,"name": "Name163"}}}}},{"id": 10,"name": "Name10","sub": {"id": 4,"name": "Name4"}}];
    
    console.log(mergeArray(input));

    【讨论】:

    • 非常感谢 trincot 的快速回答。我明天会试一试,因为我不再工作了,然后回复你!
    • 您的解决方案创造了奇迹!但是,我将接受@Nina Scholz 的,因为它更简洁。再次感谢!
    • 没错,Nina 的解决方案更简洁。请注意,由于对find 的嵌套调用,它的时间复杂度更差。我添加了 Map 的使用,它添加了额外的代码,但它有一个目的:时间复杂度降低,这在更大的数据集上可能会变得很明显。在小型数据集上,由于Map 创建开销,它会运行得更慢。
    【解决方案3】:

    我会用递归函数来做,因为我认为它们更通用:

    const data = [{
        "id": 14,
        "name": "Name14",
        "theme": true,
        "sub": {
          "id": 70,
          "name": "Name70"
        }
      },
      {
        "id": 14,
        "name": "Name14",
        "theme": true,
        "sub": {
          "id": 61,
          "name": "Name61"
        }
      },
      {
        "id": 14,
        "name": "Name14",
        "theme": true,
        "sub": {
          "id": 4,
          "name": "Name4",
          "sub": {
            "id": 5,
            "name": "Name5",
            "sub": {
              "id": 29,
              "name": "Name29"
            }
          }
        }
      },
      {
        "id": 14,
        "name": "Name14",
        "theme": true,
        "sub": {
          "id": 4,
          "name": "Name4",
          "sub": {
            "id": 5,
            "name": "Name5",
            "sub": {
              "id": 8,
              "name": "Name8",
              "sub": {
                "id": 163,
                "name": "Name163"
              }
            }
          }
        }
      },
      {
        "id": 10,
        "name": "Name10",
        "sub": {
          "id": 4,
          "name": "Name4"
        }
      }
    ]
    
    // setting up arrays
    const recursiveModify = (node) => {
      if (typeof node.sub === "undefined") {
        return node
      } else {
        node.sub = [recursiveModify(node.sub)]
        return node
      }
    }
    
    const recursiveReduce = (nodes) => {
      return nodes.reduce((a, c) => {
        const item = a.find(e => e.id === c.id)
        if (!item) {
          a.push(c)
        } else {
          item.sub = recursiveReduce([...item.sub, ...c.sub])
        }
        return a
      }, [])
    }
    
    const dataWithArray = data.map(node => {
      return recursiveModify(node)
    })
    
    const result = recursiveReduce(dataWithArray)
    console.log(result)

    不幸的是,我只能通过两次传递来做到这一点——一次用于将sub 创建为arrays,然后一次用于实际对数据进行分组。我很确定它可以一次性完成 - 我没有更多的时间来解决它。

    【讨论】:

    • 感谢您抽空muka!不过,我会接受 Nina 的回答,因为它更简洁
    • @JoPe 我也会这样做:)
    猜你喜欢
    • 2021-01-19
    • 1970-01-01
    • 2018-02-25
    • 1970-01-01
    • 2021-12-23
    • 1970-01-01
    • 2019-02-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多