Search code examples
javascriptjquerydevexpressunderscore.jslodash

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)


Solution

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