Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set data to deeply nested array

I am using a DevExtreme React Grid Tree. My initial call only populates the root row, each additional sub row is applied on click. I am having issues while applying the sub row data when there are many nested table rows. I need an efficient way to find the correct parent row and add the next nested array. Here is the table data with one nested row I have already added.

    [
  {
    "area": "Artesia",
    "list_id": 45,
    "rowId": 158324175700860960,
    "parentRowId": 0,
    "items": [
      {
        "area": "Other",
        "list_id": 15003,
        "rowId": 158324179061139520,
        "parentRowId": 158324175700860960
      }
    ]
  },
  {
    "area": "Corpus Christi",
    "list_id": 60,
    "rowId": 158324175700847800,
    "parentRowId": 0,
    "items": []
  },
  {
    "area": "Midland",
    "list_id": 10,
    "rowId": 158324175700867700,
    "parentRowId": 0,
    "items": [
      {
        "area": "Delaware Basin",
        "list_id": 11001,
        "rowId": 158324181266273440,
        "parentRowId": 158324175700867700,
        "items": []
      }
    ]
  },
  {
    "area": "San Antonio",
    "list_id": 63,
    "rowId": 158324175700814050,
    "parentRowId": 0,
    "items": []
  }
]

After clicking on the Midland row I applied the API return data as a nested array item.

    [
  {
    "area": "Delaware Basin",
    "list_id": 11001,
    "rowId": 158324181266273440,
    "parentRowId": 158324175700867700,
    "items": []
  }
]

I need a function that can loop through the table data from the root level to unlimited nested rows. I match the data now by using the parentId to match the rowId.

// root table data with one nested row applied to Midland
const tableData = [
  {
    "area": "Artesia",
    "list_id": 45,
    "rowId": 158324175700860960,
    "parentRowId": 0,
    "items": [
      {
        "area": "Other",
        "list_id": 15003,
        "rowId": 158324179061139520,
        "parentRowId": 158324175700860960
      }
    ]
  },
  {
    "area": "Corpus Christi",
    "list_id": 60,
    "rowId": 158324175700847800,
    "parentRowId": 0,
    "items": []
  },
  {
    "area": "Midland",
    "list_id": 10,
    "rowId": 158324175700867700,
    "parentRowId": 0,
    "items": [
      {
        "area": "Delaware Basin",
        "list_id": 11001,
        "rowId": 158324181266273440,
        "parentRowId": 158324175700867700,
        "items": []
      }
    ]
  },
  {
    "area": "San Antonio",
    "list_id": 63,
    "rowId": 158324175700814050,
    "parentRowId": 0,
    "items": []
  }
]

// return data from API after clicking on Delaware Basin
const returnData = [
  {
    "area": "Delaware Basin Nm",
    "list_id": 11007,
    "rowId": 158324182577224580,
    "parentRowId": 158324181266273440
  },
  {
    "area": "Delaware Basin Tx",
    "list_id": 11002,
    "rowId": 158324182577248960,
    "parentRowId": 158324181266273440
  }
]

function applyNestedData (tableData, returnData) {

}

applyNestedData(tableData, returnData)
like image 809
texas697 Avatar asked Mar 03 '20 13:03

texas697


People also ask

Can you write a function that deeply flattens an array?

flat() function. ECMA 2019 introduced a new method called flat() for recursively flatten an array. It takes the depth of the nested array as a parameter, which is 1 by default. To flatten any depth of a nested array, use the Infinity with the flat() method.

How do I make a nested array?

Creating a Nested Array: Let's create nested arrays using those three methods to get an idea of Nested Arrays. This one is just equating the variable to the array. Second one is using the array method new Array() . And the last one is using the Array() which is similar to the new Array() .


5 Answers

You may simply traverse the tree using some dfs algo

const tableData = [{ area: 'Artesia ', list_id: 45, rowId: 15836049402342958, parentRowId: 0, Premium: '785', 'Non Premium': '152', Total: '937', items: [] }, { area: 'Corpus Christi ', list_id: 60, rowId: 158360494023429300, parentRowId: 0, Total: '3,098', items: [] }, { area: 'Denver ', list_id: 30, rowId: 158360494023563870, parentRowId: 0, Total: '7,938', items: [] }, { area: 'Fort Worth ', list_id: 14, rowId: 158360494023592130, parentRowId: 0, Total: '14', items: [{ area: 'Southlake', list_id: 1256788, rowId: 12345, parentRowId: 158360494023592130, Premium: '7,743', 'Non Premium': '2,584', Total: '10,327', items: [] }] }, { area: 'Midland ', list_id: 10, rowId: 158360494023510200, parentRowId: 0, Premium: '7,743', 'Non Premium': '2,584', Total: '10,327', items: [{ area: 'Delaware Basin', list_id: 11001, rowId: 158324181266273440, parentRowId: 158360494023510200, Premium: '7,743', 'Non Premium': '2,584', Total: '10,327', items: [] }] }, { area: 'Okc ', list_id: 50, rowId: 158360494023542430, parentRowId: 0, Total: '245', items: [] }, { area: 'San Antonio ', list_id: 63, rowId: 158360494023591680, parentRowId: 0, Total: '4,162', items: [] }]
const returnData = [{ area: 'Delaware Basin Nm', list_id: 11007, rowId: 158324182577224580, parentRowId: 158324181266273440 }, { area: 'Delaware Basin Tx', list_id: 11002, rowId: 158324182577248960, parentRowId: 158324181266273440 }, { area: 'Sub Southlake', list_id: 2345, rowId: 550987654, parentRowId: 12345 }]

const byParentRowId = returnData.reduce((m, it) => {
  const v = m.get(it.parentRowId) || []
  v.push(it)
  m.set(it.parentRowId, v)
  return m
}, new Map())


const findRow = (tableData => {
  function dfs (data, stopId) {
    if (data.rowId === stopId) return data
    if (!Array.isArray(data.items)) return []
    return data.items.flatMap(x => dfs(x, stopId))
  }
  return rowId => dfs({ items: tableData }, rowId)[0]
})(tableData)

console.time('setTree1')
;[...byParentRowId.entries()].forEach(([rowId, v]) => (findRow(rowId).items = v))
console.timeEnd('setTree1')
console.log(JSON.stringify({ items: tableData }, null, 2))

Notice that for every different parentRowId you traverse the tree. If you want to be a bit savy, which comes at the cost of more code, you can just prebuild a mapping rowId => node beforehand, and maintain it upon population of your nested rows. I would advise no to do it except if you observe noticeable (and meaningful) gain. Here, it is 1ms, so useless.

const tableData = [{ area: 'Artesia ', list_id: 45, rowId: 15836049402342958, parentRowId: 0, Premium: '785', 'Non Premium': '152', Total: '937', items: [] }, { area: 'Corpus Christi ', list_id: 60, rowId: 158360494023429300, parentRowId: 0, Total: '3,098', items: [] }, { area: 'Denver ', list_id: 30, rowId: 158360494023563870, parentRowId: 0, Total: '7,938', items: [] }, { area: 'Fort Worth ', list_id: 14, rowId: 158360494023592130, parentRowId: 0, Total: '14', items: [{ area: 'Southlake', list_id: 1256788, rowId: 12345, parentRowId: 158360494023592130, Premium: '7,743', 'Non Premium': '2,584', Total: '10,327', items: [] }] }, { area: 'Midland ', list_id: 10, rowId: 158360494023510200, parentRowId: 0, Premium: '7,743', 'Non Premium': '2,584', Total: '10,327', items: [{ area: 'Delaware Basin', list_id: 11001, rowId: 158324181266273440, parentRowId: 158360494023510200, Premium: '7,743', 'Non Premium': '2,584', Total: '10,327', items: [] }] }, { area: 'Okc ', list_id: 50, rowId: 158360494023542430, parentRowId: 0, Total: '245', items: [] }, { area: 'San Antonio ', list_id: 63, rowId: 158360494023591680, parentRowId: 0, Total: '4,162', items: [] }]
const returnData = [{ area: 'Delaware Basin Nm', list_id: 11007, rowId: 158324182577224580, parentRowId: 158324181266273440 }, { area: 'Delaware Basin Tx', list_id: 11002, rowId: 158324182577248960, parentRowId: 158324181266273440 }, { area: 'Sub Southlake', list_id: 2345, rowId: 550987654, parentRowId: 12345 }]

const byParentRowId = returnData.reduce((m, it) => {
  const v = m.get(it.parentRowId) || []
  v.push(it)
  m.set(it.parentRowId, v)
  return m
}, new Map())

const table = (tableData => {
  const rows = new Map()
  function dfs (data) {
    if (data.rowId) {
      rows.set(data.rowId, data)
    }
    if (!Array.isArray(data.items)) { return }
    return data.items.forEach(dfs)
  }
  dfs({ items: tableData })
  return {
    setRow: (rowId, items) => {
      items.forEach(it => rows.set(it.rowId, it))
      const row = rows.get(rowId)
      row.items = items
    },
    getRow: rowId => rows.get(rowId)
  }
})(tableData)

console.time('setTree2')
;[...byParentRowId.entries()].forEach(([rowId, v]) => table.setRow(rowId, v))
console.timeEnd('setTree2')
console.log(JSON.stringify({items: tableData},null,2))
like image 181
grodzi Avatar answered Oct 16 '22 15:10

grodzi


I have written a solution for this problem. Hope you will enjoy.

Please check that I have created in stackblitz: https://stackblitz.com/edit/js-wurii6

const tableData = [
  {
    "area": "Artesia",
    "list_id": 45,
    "rowId": 158324175700860960,
    "parentRowId": 0,
    "items": [
      {
        "area": "Other",
        "list_id": 15003,
        "rowId": 158324179061139520,
        "parentRowId": 158324175700860960
      }
    ]
  },
  {
    "area": "Corpus Christi",
    "list_id": 60,
    "rowId": 158324175700847800,
    "parentRowId": 0,
    "items": []
  },
  {
    "area": "Midland",
    "list_id": 10,
    "rowId": 158324175700867700,
    "parentRowId": 0,
    "items": [
      {
        "area": "Delaware Basin",
        "list_id": 11001,
        "rowId": 158324181266273440,
        "parentRowId": 158324175700867700,
        "items": []
      }
    ]
  },
  {
    "area": "San Antonio",
    "list_id": 63,
    "rowId": 158324175700814050,
    "parentRowId": 0,
    "items": []
  }
];
const returnData = [
    {
      "area": "Delaware Basin Nm",
      "list_id": 11007,
      "rowId": 158324182577224580,
      "parentRowId": 158324181266273440
    },
    {
      "area": "Delaware Basin Tx",
      "list_id": 11002,
      "rowId": 158324182577248960,
      "parentRowId": 158324181266273440
    }
  ];
  

 const appResult = document.getElementById('result');

const inputWrapper = {items: tableData};
applyNestedData(inputWrapper, returnData);


function applyNestedData(tableData, returnData){
  const { parentRowId } = returnData[0];
  const datareturned = findAndUpdate(tableData, returnData, parentRowId);
  appResult.innerHTML = JSON.stringify(tableData, undefined, 4)
}

function findAndUpdate(row, returnData, parentRowId){
  if(row.rowId && row.rowId == parentRowId){
      return true;
  } else if (row.items){
      let isParent = false;
      for(let i=0; row.items && i < row.items.length; i++){
        isParent = findAndUpdate(row.items[i], returnData, parentRowId);
        if (isParent === true) {
          row.items[i].items = returnData;
          console.info("found")
          break;
        }
      }
  }
  return row;
}
<h1>Result</h1>
<pre id="result" style= 
        "color:green; font-size: 12px; font-weight: bold;"> 
</pre> 
like image 3
Prakash Avatar answered Oct 16 '22 14:10

Prakash


function applyNestedData (tableData, returnData) {
  // use a map to group data by its parentRowId
  // in order to insert them together when find matched rowId
  const map = new Map()
  for (const data of returnData) {
    if (map.has(data.parentRowId)) {
      map.get(data.parentRowId).push(data)
    } else {
      map.set(data.parentRowId, [data])
    }
  }

  // Loop tree-structure data by queue
  const queue = []
  for (const data of tableData) { queue.push(data) }

  let data
  while (queue.length > 0) {
    data = queue.shift()
    if (map.has(data.rowId)) {
      data.items = data.items.concat(map.get(data.rowId))
    }
    if (data.items && data.items.length > 1) {
      for (const item of data.items) {
        queue.push(item)
      }
    }
  }
}
like image 2
Zmen Hu Avatar answered Oct 16 '22 15:10

Zmen Hu


function applyNestedData (tableData, returnData) {
  const map = new Map();
  returnData.map(data=> {
    map.has(data.parentRowId)?
      map.get(data.parentRowId).push(data):map.set(data.parentRowId, [data])
  });
  const queue = [];
  tableData.map(data=>{queue.push(data)});

  let data;
  while (queue.length > 0) {
    data = queue.shift();
    map.has(data.rowId) && (data.items = data.items.concat(map.get(data.rowId)));
    (data.items && data.items.length > 1) && data.items.map(item=> {queue.push(item)})
  }
}
like image 2
Rajesh kumar R Avatar answered Oct 16 '22 15:10

Rajesh kumar R


You can create a recursive function like this.

let iterate = (inputdata, item) => {
  let id = inputdata.parentRowId;
  if (item.rowId == id) {
     item.items.push(inputdata);
  }
  else if (item.items && item.items.length) {
    item.items.map(obj => {
      iterate(inputdata, obj);
    });
  }
}

returnData.forEach(inputdata => {
  tableData.forEach(item => {
    iterate(inputdata, item);
  });
});
like image 1
Jeni Avatar answered Oct 16 '22 14:10

Jeni