13

I got following data structure, which is an array of accounts objects, where some accounts are being parents to its children accounts, which in turn can be parents to other accounts etc.:

[{
  "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": [

    ]
  }]
}]

What I need is to flatten this array to have a flat list of accounts. What's the way to proceed with that in Vanilla JavaScript. (I also got access to lodash methods in my project).

Rajesh
  • 24,354
  • 5
  • 48
  • 79
robjez
  • 3,740
  • 3
  • 32
  • 36
  • 2
    Just make a recursive function that iterates all children. Every time you encounter a child, add it to a new array. Shouldn't be difficult. –  Feb 08 '16 at 14:55
  • 1
    You can use `Array.forEach`, `Array.map`, `Array.reduce` – Rajesh Feb 08 '16 at 14:55
  • Thanks for quick response guys. So basically I will need to iterate over `keys` of this object, to find those with `children` property and `push` newly found accounts to a new array? I was hoping for a nice utility method ie. in `lodash` for this type of operation, but it seems that is not that straightforward. – robjez Feb 08 '16 at 15:00
  • what should happen to the children? – Nina Scholz Feb 08 '16 at 15:02
  • @robjez: No, you already know the name of the property. There's no reason to iterate to find it. Simply create a function that receives an array of objects as the first param, and the "target" array as the second. Then use a normal `for` loop to iterate the array, putting each object into the "target" array. Also in the loop, check to see if the object's `children` property has an array, and if so, make a recursive call to your function, passing that array as the first parameter, and the same "target" array as the second. –  Feb 08 '16 at 15:03
  • @NinaScholz - nothing, I just need flat list of all accounts objects – robjez Feb 08 '16 at 15:03
  • @user1106925 could you please point to an example of what you're describing? – Redink Sep 08 '22 at 03:43

7 Answers7

29

Just iterate over and if a children is found get the array from the children concatinated.

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>');
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
13

try to understand this version, with it's benefits and implications

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

the argument order might be confusing at first, unless you are used to reduced, or the concept of options first, data last, wich is very handy when it comes to FP and currying or partial application.

Thomas
  • 3,513
  • 1
  • 13
  • 10
  • this works too, seems more compact. What is the benefit of using this method above the others. Is it terseness of syntax, or are there any performance benefits of that? – robjez Feb 08 '16 at 15:49
  • 1
    this is my goto aproach for parsing kind of recursive structures. It is simple, straightforward, extensible, and can easily be modified to take any kind of input. That's also the reason i use `flatten(into, node.children)` and don't use directly `node.children.reduce(flatten, into)`; it can parse like any imaginable recursive structure of nested Arrays, nodes, and holes (null, undefined). and that out of the box. – Thomas Feb 08 '16 at 19:23
2

You could make a recursive function that looks through each account, and if it has children calls itself on the children, the function should return a flattened array of accounts.

This example is a bit more complicated than it has to be because I am assuming that order matters and that the parent should be before the children.

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

This is a simpler example but the children would end up before the parent.

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
  • 680
  • 6
  • 17
Dustin Poissant
  • 3,201
  • 1
  • 20
  • 32
1

You can loop through the array entries, check if it's an array with isArray() and recursively flatten the entries of the array as below:

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

mk23
  • 1,257
  • 1
  • 12
  • 25
1

I'd like to point out that all answers work by copying the children to the root, but leaving the original children in place, and hence creating duplicates - not a flat array.

Depending on how the data is used, this might lead to issues. I would certainly not want to be caught by my team leaving a messy array behind.

I couldn't find a solution to remove the children within the recursive function, so I added a second "cleanup" function.

Based on the accepted answer by 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));

}
boris
  • 81
  • 7
0

You can also use javascript recursive map function and get the children nodes of the nested array, like this:

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

Source Here

0

Yet another approach.. It's simple but it works.

function flattenChildren(arr: any): any {
  return arr.flatMap(({ children, ...o }: { children: any; o: any }) => [
    o,
    ...flattenChildren(children),
  ])
}
eledgaar
  • 758
  • 1
  • 9
  • 16