将深度嵌套的对象数组扁平化

13

我有以下数据结构,它是一个帐户对象数组,其中一些账户是其子账户的父账户,这些子账户又可以是其他账户的父账户等等:

[{
  "id": "acc.1260446672222.11",
  "type": "EXPENSES_FOLDER",
  "name": "Expense Group",
  "balance": 3418.11,
  "children": [{
    "id": "acc.1260446672238.27",
    "type": "EXPENSE",
    "name": "Advertising, Promotion and Entertainment Account",
    "balance": 0,
    "children": []
  }, {
    "id": "acc.9a2492ba-0d82-4f4a-a1b4-14868f1e1a39",
    "type": "EXPENSES_FOLDER",
    "name": "Premises Costs",
    "balance": 0,
    "children": [{
      "id": "acc.287ba5b6-5536-428b-950f-d71d2af73ccc",
      "type": "EXPENSE",
      "name": "Use of Home - Gas",
      "balance": 0,
      "children": [

      ]
    }, {
      "id": "acc.7091ee15-3f02-4bd1-94e5-5918cf986969",
      "type": "EXPENSE",
      "name": "Hire of Venue, Studios, Teaching Rooms",
      "balance": 0,
      "children": [

      ]
    }]
  }, {
    "id": "acc.827ec446-edeb-4f2b-8032-d306292d2d83",
    "type": "EXPENSES_FOLDER",
    "name": "Administrative Expenses",
    "balance": 558.61,
    "children": [{
      "id": "acc.0ed5fc81-7734-4452-86a9-db22a6b0f8e8",
      "type": "EXPENSE",
      "name": "Bank Charges",
      "balance": 15,
      "children": [

      ]
    }, {
      "id": "acc.e2cdb2c0-8565-4991-a35a-d4596b0ddf45",
      "type": "EXPENSE",
      "name": "Software & Computer Peripherals",
      "balance": 417.13,
      "children": [

      ]
    }, {
      "id": "acc.96d5d00e-43f4-4d3a-b97b-fdf258c65514",
      "type": "EXPENSE",
      "name": "Printing, photocopying etc",
      "balance": 55.93,
      "children": [

      ]
    }, {
      "id": "acc.494dd64a-4fb3-42b8-be3e-8f3b59a2ef59",
      "type": "EXPENSE",
      "name": "Artists Administration Service",
      "balance": 0,
      "children": [

      ]
    }, {
      "id": "acc.1260446672238.35",
      "type": "EXPENSE",
      "name": "Stationery",
      "balance": 0,
      "children": [

      ]
    }, {
      "id": "acc.96d89d0d-5465-488b-b37f-d41ca114c5e6",
      "type": "EXPENSE",
      "name": "Mobile Telephone",
      "balance": 41.19,
      "children": [

      ]
    }, {
      "id": "acc.1260446672238.33",
      "type": "EXPENSE",
      "name": "Home Telephone",
      "balance": 0,
      "children": [

      ]
    }, {
      "id": "acc.1260446672238.38",
      "type": "EXPENSE",
      "name": "Postage/delivery",
      "balance": 29.36,
      "children": [

      ]
    }]
  }, {
    "id": "acc.b9c9bbc7-43df-472e-9ac8-c7c76f08f49a",
    "type": "EXPENSES_FOLDER",
    "name": "Instruments, Equipment Maintenance etc",
    "balance": 1002.48,
    "children": [{
      "id": "acc.1260446672238.32",
      "type": "OTHER_EXPENSES",
      "name": "Instrument Insurance",
      "balance": 157.48,
      "children": [

      ]
    }, {
      "id": "acc.2a1cca15-2868-4770-a3e7-d43a6268c6a1",
      "type": "EXPENSE",
      "name": "Instrument Repairs & Maintenance",
      "balance": 845,
      "children": [

      ]
    }, {
      "id": "acc.a908aee0-84fb-450a-916b-4cec25265aef",
      "type": "EXPENSE",
      "name": "Accessories & Replacement Parts",
      "balance": 0,
      "children": [

      ]
    }]
  }, {
    "id": "acc.a42cdd86-0d9e-4f3f-af0d-7c4525374731",
    "type": "EXPENSES_FOLDER",
    "name": "Motor Vehicle",
    "balance": 0,
    "children": [{
      "id": "acc.cb325e7e-0ce4-4c78-9cb4-20659df733a6",
      "type": "EXPENSE",
      "name": "Fuel and Oil",
      "balance": 0,
      "children": [

      ]
    }]
  }, {
    "id": "acc.4bdd9e26-ce64-4e7f-b46a-82ec9de06ded",
    "type": "EXPENSES_FOLDER",
    "name": "Other Travel",
    "balance": 132.1,
    "children": [{
      "id": "acc.77dd2142-f2de-4a2c-9247-061d0661bc0a",
      "type": "EXPENSE",
      "name": "Taxis",
      "balance": 24.5,
      "children": [

      ]
    }, {
      "id": "acc.2b54abdd-7ef5-43cd-bdb9-c8c981b59ff2",
      "type": "EXPENSE",
      "name": "Public Transport",
      "balance": 107.6,
      "children": [

      ]
    }]
  }, {
    "id": "acc.e4695b70-31fa-4e23-afd0-97335dcd5b9e",
    "type": "EXPENSE",
    "name": "Subsitence",
    "balance": 0,
    "children": [

    ]
  }, {
    "id": "acc.02d222bf-4dff-4308-afe9-69b93f412ada",
    "type": "EXPENSE",
    "name": "Hotel and Accomodation",
    "balance": 0,
    "children": [

    ]
  }, {
    "id": "acc.d61cd5b4-2c80-4ab8-93d0-9d5726bd253b",
    "type": "EXPENSES_FOLDER",
    "name": "Fees and Commission Paid",
    "balance": 0,
    "children": [{
      "id": "acc.1262189019758.7",
      "type": "EXPENSE",
      "name": "Pupils exam entry fees",
      "balance": 0,
      "children": [

      ]
    }, {
      "id": "acc.a7d7efd3-d0da-4704-babb-079b6077f3fe",
      "type": "EXPENSE",
      "name": "Audition, competition entry fees",
      "balance": 0,
      "children": [

      ]
    }, {
      "id": "acc.3b91ee4e-40a8-46d8-aa05-3afa5974b3ef",
      "type": "EXPENSE",
      "name": "Deputies, Other Musicians",
      "balance": 0,
      "children": [

      ]
    }]
  }, {
    "id": "acc.250d6872-6023-4599-a0b6-b7159eebbfa1",
    "type": "EXPENSES_FOLDER",
    "name": "Other Professional Expenses",
    "balance": 1739.42,
    "children": [{
      "id": "acc.b7315228-f85a-4ffb-9199-d1128a409e5f",
      "type": "EXPENSE",
      "name": "Promotion & Publicity",
      "balance": 138.6,
      "children": [

      ]
    }, {
      "id": "acc.69ca2005-d7a0-448b-b70c-dafb128a48ae",
      "type": "EXPENSE",
      "name": "Other Expenses",
      "balance": 364.5,
      "children": [

      ]
    }, {
      "id": "acc.dcd999d2-4e18-41be-b9cc-218d4034b88e",
      "type": "EXPENSE",
      "name": "Office Equipment, Furniture",
      "balance": 0,
      "children": [

      ]
    }, {
      "id": "acc.e0460706-d5c9-4c40-9d1e-0d2058864b92",
      "type": "EXPENSE",
      "name": "CDs, Dowloads etc",
      "balance": 67.57,
      "children": [

      ]
    }, {
      "id": "acc.1866df79-9e44-459a-a978-727904987469",
      "type": "EXPENSE",
      "name": "Professional Books, Magazines",
      "balance": 104.01,
      "children": [

      ]
    }, {
      "id": "acc.24c1651d-e7ae-48bc-a32d-311427e0fcea",
      "type": "EXPENSE",
      "name": "Professional Associations",
      "balance": 272.17,
      "children": [

      ]
    }, {
      "id": "acc.289ab0ac-b9d3-435e-ac82-9da9702b7d4b",
      "type": "EXPENSE",
      "name": "Tuition",
      "balance": 470,
      "children": [

      ]
    }, {
      "id": "acc.f24cf99b-6291-4b9f-821e-425f4909d4e1",
      "type": "EXPENSE",
      "name": "Scores, Manuscript Paper etc",
      "balance": 215.32,
      "children": [

      ]
    }, {
      "id": "acc.1af95953-56f0-455e-9d0a-7c4e0477cf0d",
      "type": "EXPENSE",
      "name": "Performance Clothing",
      "balance": 0,
      "children": [

      ]
    }, {
      "id": "acc.c0585577-535a-4ae2-a02b-e5b249f67c67",
      "type": "EXPENSE",
      "name": "Concerts, Shows etc",
      "balance": 107.25,
      "children": [

      ]
    }]
  }, {
    "id": "acc.1260446672222.24",
    "type": "ADMIN",
    "name": "Administrative Expenses",
    "balance": 0,
    "children": [

    ]
  }, {
    "id": "acc.1260446672238.26",
    "type": "TRAVEL",
    "name": "Travel and Subsistence Account",
    "balance": -14.5,
    "children": [

    ]
  }, {
    "id": "acc.1260446672238.28",
    "type": "LEGAL",
    "name": "Legal and Professional Costs Account",
    "balance": 0,
    "children": [

    ]
  }, {
    "id": "acc.1260446672238.36",
    "type": "OTHER_EXPENSES",
    "name": "Rent/Rates",
    "balance": 0,
    "children": [

    ]
  }, {
    "id": "acc.1262191376548.37",
    "type": "EXPENSE",
    "name": "Research",
    "balance": 0,
    "children": [

    ]
  }, {
    "id": "acc.1262191388329.38",
    "type": "EXPENSE",
    "name": "Professional Development",
    "balance": 0,
    "children": [

    ]
  }, {
    "id": "acc.1262192291558.52",
    "type": "EXPENSE",
    "name": "Professional Presentation",
    "balance": 0,
    "children": [

    ]
  }, {
    "id": "acc.1262193596634.72",
    "type": "EXPENSE",
    "name": "Subscriptions",
    "balance": 0,
    "children": [

    ]
  }, {
    "id": "acc.1262265941130.16",
    "type": "EXPENSE",
    "name": "Piano accompaniment",
    "balance": 0,
    "children": [

    ]
  }, {
    "id": "acc.1267370824329.1",
    "type": "EXPENSE",
    "name": "Cost of Sales",
    "balance": 0,
    "children": [

    ]
  }]
}]
我需要的是将这个数组展平为一个帐户列表。在 Vanilla JavaScript 中如何操作?(我还可以在项目中使用 lodash 方法。)

2
只需编写一个递归函数来迭代所有子元素。每次遇到一个子元素,就将其添加到一个新数组中。这应该不难。 - user1106925
1
你可以使用 Array.forEachArray.mapArray.reduce - Rajesh
谢谢大家的快速回复。所以基本上我需要迭代这个对象的“keys”,找到那些具有“children”属性并将新发现的帐户“push”到一个新数组中?我希望有一个很好的实用方法,比如在“lodash”中进行这种操作,但似乎这并不是那么简单明了的。 - robjez
孩子们应该发生什么事情? - Nina Scholz
@robjez:不,你已经知道属性的名称了。没有必要迭代查找它。只需创建一个函数,将对象数组作为第一个参数,"目标"数组作为第二个参数传递进去。然后使用普通的for循环来迭代数组,将每个对象放入"目标"数组中。同时,在循环中,检查对象的children属性是否有一个数组,如果有,就对你的函数进行递归调用,将该数组作为第一个参数传递进去,将相同的"目标"数组作为第二个参数传递进去。 - user1106925
显示剩余2条评论
7个回答

29
只需迭代,如果找到子元素,则从子元素中获取连接的数组。

function flat(array) {
    var result = [];
    array.forEach(function (a) {
        result.push(a);
        if (Array.isArray(a.children)) {
            result = result.concat(flat(a.children));
        }
    });
    return result;
}

var data = [{ id: "acc.1260446672222.11", type: "EXPENSES_FOLDER", name: "Expense Group", balance: 3418.11, children: [{ id: "acc.1260446672238.27", type: "EXPENSE", name: "Advertising, Promotion and Entertainment Account", balance: 0, children: [] }, { id: "acc.9a2492ba-0d82-4f4a-a1b4-14868f1e1a39", type: "EXPENSES_FOLDER", name: "Premises Costs", balance: 0, children: [{ id: "acc.287ba5b6-5536-428b-950f-d71d2af73ccc", type: "EXPENSE", name: "Use of Home - Gas", balance: 0, children: [] }, { id: "acc.7091ee15-3f02-4bd1-94e5-5918cf986969", type: "EXPENSE", name: "Hire of Venue, Studios, Teaching Rooms", balance: 0, children: [] }] }, { id: "acc.827ec446-edeb-4f2b-8032-d306292d2d83", type: "EXPENSES_FOLDER", name: "Administrative Expenses", balance: 558.61, children: [{ id: "acc.0ed5fc81-7734-4452-86a9-db22a6b0f8e8", type: "EXPENSE", name: "Bank Charges", balance: 15, children: [] }, { id: "acc.e2cdb2c0-8565-4991-a35a-d4596b0ddf45", type: "EXPENSE", name: "Software & Computer Peripherals", balance: 417.13, children: [] }, { id: "acc.96d5d00e-43f4-4d3a-b97b-fdf258c65514", type: "EXPENSE", name: "Printing, photocopying etc", balance: 55.93, children: [] }, { id: "acc.494dd64a-4fb3-42b8-be3e-8f3b59a2ef59", type: "EXPENSE", name: "Artists Administration Service", balance: 0, children: [] }, { id: "acc.1260446672238.35", type: "EXPENSE", name: "Stationery", balance: 0, children: [] }, { id: "acc.96d89d0d-5465-488b-b37f-d41ca114c5e6", type: "EXPENSE", name: "Mobile Telephone", balance: 41.19, children: [] }, { id: "acc.1260446672238.33", type: "EXPENSE", name: "Home Telephone", balance: 0, children: [] }, { id: "acc.1260446672238.38", type: "EXPENSE", name: "Postage/delivery", balance: 29.36, children: [] }] }, { id: "acc.b9c9bbc7-43df-472e-9ac8-c7c76f08f49a", type: "EXPENSES_FOLDER", name: "Instruments, Equipment Maintenance etc", balance: 1002.48, children: [{ id: "acc.1260446672238.32", type: "OTHER_EXPENSES", name: "Instrument Insurance", balance: 157.48, children: [] }, { id: "acc.2a1cca15-2868-4770-a3e7-d43a6268c6a1", type: "EXPENSE", name: "Instrument Repairs & Maintenance", balance: 845, children: [] }, { id: "acc.a908aee0-84fb-450a-916b-4cec25265aef", type: "EXPENSE", name: "Accessories & Replacement Parts", balance: 0, children: [] }] }, { id: "acc.a42cdd86-0d9e-4f3f-af0d-7c4525374731", type: "EXPENSES_FOLDER", name: "Motor Vehicle", balance: 0, children: [{ id: "acc.cb325e7e-0ce4-4c78-9cb4-20659df733a6", type: "EXPENSE", name: "Fuel and Oil", balance: 0, children: [] }] }, { id: "acc.4bdd9e26-ce64-4e7f-b46a-82ec9de06ded", type: "EXPENSES_FOLDER", name: "Other Travel", balance: 132.1, children: [{ id: "acc.77dd2142-f2de-4a2c-9247-061d0661bc0a", type: "EXPENSE", name: "Taxis", balance: 24.5, children: [] }, { id: "acc.2b54abdd-7ef5-43cd-bdb9-c8c981b59ff2", type: "EXPENSE", name: "Public Transport", balance: 107.6, children: [] }] }, { id: "acc.e4695b70-31fa-4e23-afd0-97335dcd5b9e", type: "EXPENSE", name: "Subsitence", balance: 0, children: [] }, { id: "acc.02d222bf-4dff-4308-afe9-69b93f412ada", type: "EXPENSE", name: "Hotel and Accomodation", balance: 0, children: [] }, { id: "acc.d61cd5b4-2c80-4ab8-93d0-9d5726bd253b", type: "EXPENSES_FOLDER", name: "Fees and Commission Paid", balance: 0, children: [{ id: "acc.1262189019758.7", type: "EXPENSE", name: "Pupils exam entry fees", balance: 0, children: [] }, { id: "acc.a7d7efd3-d0da-4704-babb-079b6077f3fe", type: "EXPENSE", name: "Audition, competition entry fees", balance: 0, children: [] }, { id: "acc.3b91ee4e-40a8-46d8-aa05-3afa5974b3ef", type: "EXPENSE", name: "Deputies, Other Musicians", balance: 0, children: [] }] }, { id: "acc.250d6872-6023-4599-a0b6-b7159eebbfa1", type: "EXPENSES_FOLDER", name: "Other Professional Expenses", balance: 1739.42, children: [{ id: "acc.b7315228-f85a-4ffb-9199-d1128a409e5f", type: "EXPENSE", name: "Promotion & Publicity", balance: 138.6, children: [] }, { id: "acc.69ca2005-d7a0-448b-b70c-dafb128a48ae", type: "EXPENSE", name: "Other Expenses", balance: 364.5, children: [] }, { id: "acc.dcd999d2-4e18-41be-b9cc-218d4034b88e", type: "EXPENSE", name: "Office Equipment, Furniture", balance: 0, children: [] }, { id: "acc.e0460706-d5c9-4c40-9d1e-0d2058864b92", type: "EXPENSE", name: "CDs, Dowloads etc", balance: 67.57, children: [] }, { id: "acc.1866df79-9e44-459a-a978-727904987469", type: "EXPENSE", name: "Professional Books, Magazines", balance: 104.01, children: [] }, { id: "acc.24c1651d-e7ae-48bc-a32d-311427e0fcea", type: "EXPENSE", name: "Professional Associations", balance: 272.17, children: [] }, { id: "acc.289ab0ac-b9d3-435e-ac82-9da9702b7d4b", type: "EXPENSE", name: "Tuition", balance: 470, children: [] }, { id: "acc.f24cf99b-6291-4b9f-821e-425f4909d4e1", type: "EXPENSE", name: "Scores, Manuscript Paper etc", balance: 215.32, children: [] }, { id: "acc.1af95953-56f0-455e-9d0a-7c4e0477cf0d", type: "EXPENSE", name: "Performance Clothing", balance: 0, children: [] }, { id: "acc.c0585577-535a-4ae2-a02b-e5b249f67c67", type: "EXPENSE", name: "Concerts, Shows etc", balance: 107.25, children: [] }] }, { id: "acc.1260446672222.24", type: "ADMIN", name: "Administrative Expenses", balance: 0, children: [] }, { id: "acc.1260446672238.26", type: "TRAVEL", name: "Travel and Subsistence Account", balance: -14.5, children: [] }, { id: "acc.1260446672238.28", type: "LEGAL", name: "Legal and Professional Costs Account", balance: 0, children: [] }, { id: "acc.1260446672238.36", type: "OTHER_EXPENSES", name: "Rent/Rates", balance: 0, children: [] }, { id: "acc.1262191376548.37", type: "EXPENSE", name: "Research", balance: 0, children: [] }, { id: "acc.1262191388329.38", type: "EXPENSE", name: "Professional Development", balance: 0, children: [] }, { id: "acc.1262192291558.52", type: "EXPENSE", name: "Professional Presentation", balance: 0, children: [] }, { id: "acc.1262193596634.72", type: "EXPENSE", name: "Subscriptions", balance: 0, children: [] }, { id: "acc.1262265941130.16", type: "EXPENSE", name: "Piano accompaniment", balance: 0, children: [] }, { id: "acc.1267370824329.1", type: "EXPENSE", name: "Cost of Sales", balance: 0, children: [] }] }],
    result = flat(data);

document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');


谢谢@NinaScholz - 它有效。现在我会花一些时间消化第6行,作为递归调用的可能候选项 :) - robjez
啊 - 这是我的下一个问题(我可能会在这个线程中再注入一波愤怒),但你是指children作为我的对象属性还是它是某个javascript对象的内置属性? - robjez
@robjez,这是你的财产。 - Nina Scholz
@NinaScholz 矩阵大师 - Herbi Shtini

14

尝试理解这个版本,包括它的好处和影响。

function flatten(into, node){
    if(node == null) return into;
    if(Array.isArray(node)) return node.reduce(flatten, into);
    into.push(node);
    return flatten(into, node.children);
}

var out = flatten([], yourArray);

刚开始时可能会让人感到混乱的是参数顺序,除非你已经熟悉了某个被简化的东西或者“选项在前,数据在后”这个概念。当涉及到函数式编程、柯里化或部分应用时,这个概念非常有用。


这种方法也可以工作,看起来更紧凑。使用这种方法的好处是什么?是语法简洁性,还是有任何性能优势? - robjez
1
这是我解析递归结构的常用方法。它简单、直接、可扩展,并且可以轻松修改以接受任何类型的输入。这也是我使用 flatten(into, node.children) 而不是直接使用 node.children.reduce(flatten, into) 的原因;它可以解析任何想象得到的嵌套数组、节点和空洞(null、undefined)的递归结构,而且这是开箱即用的。 - Thomas

2
你可以编写一个递归函数,遍历每个账户,如果它有子账户则在子账户上调用自身。该函数应返回一个扁平化的账户数组。
这个例子比实际需要的要复杂一些,因为我假设顺序很重要,且父账户应该在子账户之前。

function flattenAccounts(accounts){
  var a = [];
  for(var i=0;i<accounts.length;i++){
    var o = accounts[i];
    if(o.children){
      var c = flattenAccounts(o.children);
      if(c){
          a = a.concat(c);
      }
    }
    a.push(o)
  }
  return a;
}

这是一个更简单的示例,但是子元素将排在父元素之前。

function flattenAccounts(accounts){
  var a = [];
  for(var i=0;i<accounts.length;i++){
    if(accounts[i].children){
      a = a.concat(flattenAccounts(accounts[i].children) )
    }
    a.push(accounts[i])
  }
  return a;
}


撇开争论不谈,你的答案甚至在样本数据中都无法正常工作。 - Gilgamesh
@DustinPoissant - 很遗憾,我无法在我的数据集上使您的函数正常工作。 - robjez

1
我想指出,所有答案都通过将子元素复制到根目录,但是保留原始子元素的位置,从而创建重复项-而不是一个平坦的数组。
根据数据使用方式的不同,这可能会导致问题。我确定不想被团队发现留下一堆混乱的数组。
我无法找到在递归函数内部删除子元素的解决方案,因此我添加了第二个“清理”函数。
基于Nina Scholz所接受的答案:
function flattenArr(arr) {

  var dirtyArr = function(arr) {
    let result = [];

    arr.forEach((arrItem) => {
      result.push(arrItem);

      if (Array.isArray(arrItem.children)) {
        result = result.concat(dirtyArr(arrItem.children));
      }
    });

    return result;
  };

  var cleanArr = function(arr) {
    arr.forEach((arrItem) => {
      if (arrItem.hasOwnProperty('children')) {
        delete arrItem.children;
      }
    });
    return arr;
  };

  return cleanArr(dirtyArr(arr));

}

1
你可以遍历数组条目,使用isArray()检查是否为数组,并递归地展平数组的条目,如下所示:
function deepFlattenArray(arr) {
  const finalArray = [];
  // Loop through the array contents
  for (let i = 0; i < arr.length; i++) {
    if (Array.isArray(arr[i])) {
      // Recursively flatten entries that are arrays and push into the finalArray
      finalArray.push(...deepFlattenArray(arr[i]));
    } else {
      // Push entries that are not arrays
      finalArray.push(arr[i]);
    }
  }
  return finalArray;
}


0
你也可以使用JavaScript递归映射函数,并获取嵌套数组的子节点,就像这样:
const dataTree = [//array above];
const getMembers = (members) => {
  let children = [];
  const flattenMembers = members.map(m => {
    if (m.children && m.children.length) {
      children = [...children, ...m.children];
    }
    return m;
  });

  return flattenMembers.concat(children.length ? getMembers(children) : children);
};

getMembers(dataTree);

这里源码


0
另一种方法...它很简单,但有效。
function flattenChildren(arr: any): any {
  return arr.flatMap(({ children, ...o }: { children: any; o: any }) => [
    o,
    ...flattenChildren(children),
  ])
}

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