将带有父ID的对象数组转换为嵌套树形结构

5

我有一个像下面这样的模拟JSON:

const apiData = [{
        "id": 1,
        "label": "List item 1",
        "parent_id": 0
    },
    {
        "id": 5,
        "label": "List item 1",
        "parent_id": 1
    },
    {
        "id": 6,
        "label": "List item 1",
        "parent_id": 1
    },
    {
        "id": 7,
        "label": "List item 1",
        "parent_id": 1
    },
    {
        "id": 8,
        "label": "List item 1",
        "parent_id": 1
    },
    {
        "id": 9,
        "label": "List item 1",
        "parent_id": 8
    },
    {
        "id": 10,
        "label": "List item 1",
        "parent_id": 8
    },
    {
        "id": 2,
        "label": "List item 1",
        "parent_id": 0
    }
]

我需要将其转换为以下内容:

[{
        "id": 1,
        "label": "List item 1",
        "parent_id": 0,
        "children": [{
                "id": 5,
                "label": "List item 1",
                "parent_id": 1
            },
            {
                "id": 6,
                "label": "List item 1",
                "parent_id": 1
            },
            {
                "id": 7,
                "label": "List item 1",
                "parent_id": 1
            },
            {
                "id": 8,
                "label": "List item 1",
                "parent_id": 1,
                "children": [{
                        "id": 9,
                        "label": "List item 1",
                        "parent_id": 8
                    },
                    {
                        "id": 10,
                        "label": "List item 1",
                        "parent_id": 8
                    }
                ]
            }

        ]
    },
    {
        "id": 2,
        "label": "List item 1",
        "parent_id": 0
    }
]

需要进行更改的条件如下: 如果对应于特定的 id,如果我们有一个 parent_id,那么我们需要在个别对象中添加一个属性 children,并将值放置为匹配的 parent_id 对象的数组。
我尝试编写了这个代码,并且已经非常接近。但是我无法继续前进。
请给予建议。
我的代码:
const apiData = [
{'id': 1, 'label': 'List item 1', 'parent_id' : 0 },
{'id': 5, 'label': 'List item 1', 'parent_id' : 1 },
{'id': 6, 'label': 'List item 1', 'parent_id' : 1 },
{'id': 7, 'label': 'List item 1', 'parent_id' : 1 },
{'id': 8, 'label': 'List item 1', 'parent_id' : 1},
{'id': 9, 'label': 'List item 1', 'parent_id' : 8 },
{'id': 10, 'label': 'List item 1', 'parent_id' : 8 },
{'id': 2, 'label': 'List item 1', 'parent_id' : 0 },
];

function compare(a, b) {
  const idA = a.id;
  const idB = b.id;
  let comparison = 0;
  comparison = idA > idB ? 1 : (idA < idB ? -1 : 0);
  return comparison;
}

const sortedApiData = apiData.sort(compare);
const newSortedApiData = [...sortedApiData];
let a = []; 


for(let i = 0; i < sortedApiData.length ; i++){
  for(let j = 0 ; j < sortedApiData.length ; j++){
     if(i === j){
       continue;
     }
     else{
       if(sortedApiData[i].id === sortedApiData[j].parent_id){
         a.push(sortedApiData[j]);

       }
     }
  }

}

console.log(a);


1
JSON是一个用于引用字符串的术语。一旦将JSON解析为内存数据结构,它就是一个普通的JS对象。在这里想要的结果是n元树。 - ggorlen
你看到我下面的新回答了吗? - Mister Jojo
是的,我看到了你的答案并接受了它。非常感谢你提供这个漂亮的代码。 - Kabeer Singh
2个回答

3
您可以通过遍历数组并将节点分配给一个包含节点的对象来构建结果树,必要时创建子数组。
构建树后,假设数据格式良好,并且至少有一个节点的父ID不存在于树中(在您的数据数组中没有这样的id:“0”节点),则返回该节点的子项。仅当每个级别应按特定方式排序时才需要排序。
如果数据格式不规范,则结果应为引用不存在的父级的每个节点的合并子节点,但在存在动机之前我将省略此内容。
最后,由于sort和reduce操作会改变输入,因此我们可以调用.map(e => ({...e}))来创建副本并保持函数纯净。

const unflatten = data => {
  const tree = data.map(e => ({...e}))
    .sort((a, b) => a.id - b.id)
    .reduce((a, e) => {
      a[e.id] = a[e.id] || e;
      a[e.parent_id] = a[e.parent_id] || {};
      const parent = a[e.parent_id];
      parent.children = parent.children || [];
      parent.children.push(e);
      return a;
    }, {})
  ;
  return Object.values(tree)
    .find(e => e.id === undefined).children;
};

const apiData = [{
    "id": 1,
    "label": "List item 1",
    "parent_id": 0
  },
  {
    "id": 9,
    "label": "List item 1",
    "parent_id": 8
  },
  {
    "id": 8,
    "label": "List item 1",
    "parent_id": 1
  },
  {
    "id": 5,
    "label": "List item 1",
    "parent_id": 1
  },
  {
    "id": 6,
    "label": "List item 1",
    "parent_id": 1
  },
  {
    "id": 7,
    "label": "List item 1",
    "parent_id": 1
  },
  {
    "id": 10,
    "label": "List item 1",
    "parent_id": 8
  },
  {
    "id": 2,
    "label": "List item 1",
    "parent_id": 0
  }
];

const expected = [{
    "id": 1,
    "label": "List item 1",
    "parent_id": 0,
    "children": [{
        "id": 5,
        "label": "List item 1",
        "parent_id": 1
      },
      {
        "id": 6,
        "label": "List item 1",
        "parent_id": 1
      },
      {
        "id": 7,
        "label": "List item 1",
        "parent_id": 1
      },
      {
        "id": 8,
        "label": "List item 1",
        "parent_id": 1,
        "children": [{
            "id": 9,
            "label": "List item 1",
            "parent_id": 8
          },
          {
            "id": 10,
            "label": "List item 1",
            "parent_id": 8
          }
        ]
      }

    ]
  },
  {
    "id": 2,
    "label": "List item 1",
    "parent_id": 0
  }
];

const unflattened = unflatten(apiData);
console.log("Matches expected? " + 
  (JSON.stringify(unflattened) === JSON.stringify(expected)));
console.log(unflattened);


2
非常感谢。那完美地回答了我的问题。我卡了两个多小时。 - Kabeer Singh
这段代码唯一的缺陷是它向原始数组apiData添加了不需要的“children”元素。 - Mister Jojo
只有在数组正确排序时,此方法才有效。如果带有子元素的元素位于数组底部,脚本将会出错。 - undefined
@yeln 这是我在回答中讨论过的一个明确决定,所以没有必要在评论中再次指出。由于原始问题中的数据格式良好,我做出了一个判断,选择保持算法简单,而不是过早地进行泛化,这对于原始问题似乎并不必要。 - undefined

1

这是一个类似的案例,但涉及到Jso项。
使用循环/递归创建对象的Javascript列表
假定这些项都正常工作。

const apiData =
      [ { id: 1,  label: 'List item 1',  parent_id: 0 }
      , { id: 5,  label: 'List item 1',  parent_id: 1 }
      , { id: 6,  label: 'List item 1',  parent_id: 1 }
      , { id: 7,  label: 'List item 1',  parent_id: 1 }
      , { id: 8,  label: 'List item 1',  parent_id: 1 }
      , { id: 9,  label: 'List item 1',  parent_id: 8 }
      , { id: 10, label: 'List item 1',  parent_id: 8 }
      , { id: 2,  label: 'List item 1',  parent_id: 0 }
      ];

const expected = 
      [ { id: 1, label: 'List item 1', parent_id: 0, children: 
          [ { id: 5, label: 'List item 1', parent_id: 1 } 
          , { id: 6, label: 'List item 1', parent_id: 1 } 
          , { id: 7, label: 'List item 1', parent_id: 1 } 
          , { id: 8, label: 'List item 1', parent_id: 1, children: 
              [ { id: 9,  label: 'List item 1', parent_id: 8 } 
              , { id: 10, label: 'List item 1', parent_id: 8 } 
        ] } ] } 
      , { id: 2, label: 'List item 1', parent_id: 0 } 
      ]; 
  
let output = []
  , pArr   = [{arr:output,id:0}]
  ;
for (let el of apiData)
  {
  let idx = pArr.findIndex(p=>p.id===el.parent_id);
  if(!Array.isArray(pArr[idx].arr))
    { pArr[idx].arr = pArr[idx].arr.children = [] }
  pArr[idx].arr.push(nv = Object.assign({}, el) )
  pArr[++idx] = { arr: nv, id:el.id }  // possible parent
  }

console.log ('output is expected ?', (JSON.stringify(output) === JSON.stringify(expected)))
console.log( 'output', output )

"

记录一下:

我写了相同的代码,但放在了Array.prototype.reduce中:

"
let result = apiData.reduce((pArr,el,ix)=>
  {
  if (Number.isInteger(pArr))   // on ix===0
    { pArr = [{arr:[],id:0,ln:--pArr}]}

  let idx = pArr.findIndex(p=>p.id===el.parent_id);
  if(!Array.isArray(pArr[idx].arr))
    { pArr[idx].arr = pArr[idx].arr.children = [] }

  pArr[idx].arr.push(nv = Object.assign({}, el) )
  pArr[++idx] = { arr: nv, id:el.id }  // possible parent

  return (ix<pArr[0].ln) ? pArr : pArr[0].arr
  }
  , apiData.length ); 

// proof:
console.log ('result is expected ?', (JSON.stringify(result) === JSON.stringify(expected)))

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