javascript - Conversia unui array de obiecte cu id-uri părinte într-o structură de arbore imbricată


Am un JSON simulativ ca cel de mai jos:

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
    }
]

și trebuie să îl convertez la mai jos:

[{
        "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
    }
]

Condiția pe baza căreia trebuie să facem modificările este următoarea: dacă pentru un anumit id, dacă avem un parent_id, atunci trebuie să adăugăm o proprietate children în obiectul individual și să introducem valorile ca un array de obiecte cu parent_id potrivit.

Am încercat să scriu cod pentru asta și m-am apropiat foarte mult, dar nu reușesc să merg mai departe.

Vă rog să sugerați.

Codul meu:

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);

Răspuns #1

Poți construi arborele rezultat parcurgând tabloul o singură dată și atribuind noduri unui obiect care conține noduri după id, creând tablouri de copii după cum este necesar.

După construirea arborelui, presupunând că datele sunt bine formate și există cel puțin un nod al cărui id de părinte nu există în arbore (în array-ul tău de date, nu există un astfel de nod id: "0"), returnează copiii acelui nod. Ordinea este necesară doar dacă fiecare nivel trebuie să fie aranjat într-un anumit fel.

Este puțin mai complicat dacă datele nu sunt bine formate; în acest caz, rezultatul ar trebui să fie copiii îmbinați ai fiecărui nod care face referire la un părinte inexistent, dar voi omite acest lucru până când va exista motivație.

În cele din urmă, deoarece operațiunile sort și reduce modifică datele de intrare, putem apela .map(e => ({...e})) pentru a crea o copie și a păstra funcția pură.

const unflatten = data => {
  const tree = data.map(e => ({...e}))
    .sort((a, b) => a.id - b.id)
    .reduce((a, e) => {
      a[e.id] ??= e;
      a[e.parent_id] ??= {};
      const parent = a[e.parent_id];
      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);

Răspunsul #2

acesta este un caz similar cu acesta, dar cu itemi jso.
Buclă/recursiune în lista Javascript pentru a crea un obiect
se presupune că itemii sunt în stare bună de funcționare

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 )

"pentru evidență": Am făcut același cod, dar plasat în interiorul unui 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)))