Deep Flatten JavaScript Object Recursively

16,421

Solution 1

A solution in plain Javascript with respect to the items. It does not mutate the source array.

function flat(r, a) {
    var b = {};
    Object.keys(a).forEach(function (k) {
        if (k !== 'items') {
            b[k] = a[k];
        }
    });
    r.push(b);
    if (Array.isArray(a.items)) {
        b.items = a.items.map(function (a) { return a.id; });
        return a.items.reduce(flat, r);
    }
    return r;
}

var data = [{ "id": 1, "level": "1", "text": "Sammy", "type": "Item", "items": [{ "id": 11, "level": "2", "text": "Table", "type": "Item", "items": [{ "id": 111, "level": "3", "text": "Dog", "type": "Item", "items": null }, { "id": 112, "level": "3", "text": "Cat", "type": "Item", "items": null }] }, { "id": 12, "level": "2", "text": "Chair", "type": "Item", "items": [{ "id": 121, "level": "3", "text": "Dog", "type": "Item", "items": null }, { "id": 122, "level": "3", "text": "Cat", "type": "Item", "items": null }] }] }, { "id": 2, "level": "1", "text": "Sundy", "type": "Item", "items": [{ "id": 21, "level": "2", "text": "MTable", "type": "Item", "items": [{ "id": 211, "level": "3", "text": "MTDog", "type": "Item", "items": null }, { "id": 212, "level": "3", "text": "MTCat", "type": "Item", "items": null }] }, { "id": 22, "level": "2", "text": "MChair", "type": "Item", "items": [{ "id": 221, "level": "3", "text": "MCDog", "type": "Item", "items": null }, { "id": 222, "level": "3", "text": "MCCat", "type": "Item", "items": null }] }] }, { "id": 3, "level": "1", "text": "Bruce", "type": "Folder", "items": [{ "id": 31, "level": "2", "text": "BTable", "type": "Item", "items": [{ "id": 311, "level": "3", "text": "BTDog", "type": "Item", "items": null }, { "id": 312, "level": "3", "text": "BTCat", "type": "Item", "items": null }] }, { "id": 32, "level": "2", "text": "Chair", "type": "Item", "items": [{ "id": 321, "level": "3", "text": "BCDog", "type": "Item", "items": null }, { "id": 322, "level": "3", "text": "BCCat", "type": "Item", "items": null }] }] }];

document.write('<pre>' + JSON.stringify(data.reduce(flat, []), 0, 4) + '</pre>');

Solution 2

With a bit of ES6 flavor

function flatten(xs) {
  return xs.reduce((acc, x) => {
    acc = acc.concat(x);
    if (x.items) {
      acc = acc.concat(flatten(x.items));
      x.items = [];
    }
    return acc;
  }, []);
}

Solution 3

Using _.flatMapDeep (available since Lodash 4.7):

var flatten = function(item) {
  return [item, _.flatMapDeep(item.items, flatten)];
}

var result = _.flatMapDeep(data, flatten);

Solution 4

A shorter solution using reduce and recursion

function flatten(data){
  return data.reduce(function(result,next){
    result.push(next);
    if(next.items){
      result = result.concat(flatten(next.items));  
      next.items = [];
    }
    return result;
  },[]);
}

var data = [
    {
      "id": 1,
      "level": "1",
      "text": "Sammy",
      "type": "Item",
      "items": [
        {
          "id": 11,
          "level": "2",
          "text": "Table",
          "type": "Item",
          "items": [
            {
              "id": 111,
              "level": "3",
              "text": "Dog",
              "type": "Item",
              "items": null
            },
            {
              "id": 112,
              "level": "3",
              "text": "Cat",
              "type": "Item",
              "items": null
            }
          ]
        },
        {
          "id": 12,
          "level": "2",
          "text": "Chair",
          "type": "Item",
          "items": [
            {
              "id": 121,
              "level": "3",
              "text": "Dog",
              "type": "Item",
              "items": null
            },
            {
              "id": 122,
              "level": "3",
              "text": "Cat",
              "type": "Item",
              "items": null
            }
          ]
        }
      ]
    },
    {
      "id": 2,
      "level": "1",
      "text": "Sundy",
      "type": "Item",
      "items": [
        {
          "id": 21,
          "level": "2",
          "text": "MTable",
          "type": "Item",
          "items": [
            {
              "id": 211,
              "level": "3",
              "text": "MTDog",
              "type": "Item",
              "items": null
            },
            {
              "id": 212,
              "level": "3",
              "text": "MTCat",
              "type": "Item",
              "items": null
            }
          ]
        },
        {
          "id": 22,
          "level": "2",
          "text": "MChair",
          "type": "Item",
          "items": [
            {
              "id": 221,
              "level": "3",
              "text": "MCDog",
              "type": "Item",
              "items": null
            },
            {
              "id": 222,
              "level": "3",
              "text": "MCCat",
              "type": "Item",
              "items": null
            }
          ]
        }
      ]
    },
    {
      "id": 3,
      "level": "1",
      "text": "Bruce",
      "type": "Folder",
      "items": [
        {
          "id": 31,
          "level": "2",
          "text": "BTable",
          "type": "Item",
          "items": [
            {
              "id": 311,
              "level": "3",
              "text": "BTDog",
              "type": "Item",
              "items": null
            },
            {
              "id": 312,
              "level": "3",
              "text": "BTCat",
              "type": "Item",
              "items": null
            }
          ]
        },
        {
          "id": 32,
          "level": "2",
          "text": "Chair",
          "type": "Item",
          "items": [
            {
              "id": 321,
              "level": "3",
              "text": "BCDog",
              "type": "Item",
              "items": null
            },
            {
              "id": 322,
              "level": "3",
              "text": "BCCat",
              "type": "Item",
              "items": null
            }
          ]
        }
      ]
    }
  ];

var result = flatten(data);

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

Solution 5

Here is solution using recursive function which I called flattenNestedObjectsArray()(for native JavaScript):

function flattenNestedObjectsArray(arr, part){
    var flattened = part || [], items;
    arr.forEach(function(v){
        if (Array.isArray(v.items) && v.items.length) {
            items = v.items;
            v.items = [];
            flattened.push(v);
            flattened.concat(flattened, flattenNestedObjectsArray(items, flattened));                
        } else {
            flattened.push(v);
        }        
    });
    return flattened;
}

var flattened = flattenNestedObjectsArray(data);
console.log(JSON.stringify(flattened, 0, 4));

The console.log output:

[
    {
        "id": 1,
        "level": "1",
        "text": "Sammy",
        "type": "Item",
        "items": []
    },
    {
        "id": 11,
        "level": "2",
        "text": "Table",
        "type": "Item",
        "items": []
    },
    {
        "id": 111,
        "level": "3",
        "text": "Dog",
        "type": "Item",
        "items": null
    },
    {
        "id": 112,
        "level": "3",
        "text": "Cat",
        "type": "Item",
        "items": null
    },
    {
        "id": 12,
        "level": "2",
        "text": "Chair",
        "type": "Item",
        "items": []
    },
    {
        "id": 121,
        "level": "3",
        "text": "Dog",
        "type": "Item",
        "items": null
    },
    {
        "id": 122,
        "level": "3",
        "text": "Cat",
        "type": "Item",
        "items": null
    },
    {
        "id": 2,
        "level": "1",
        "text": "Sundy",
        "type": "Item",
        "items": []
    },
    {
        "id": 21,
        "level": "2",
        "text": "MTable",
        "type": "Item",
        "items": []
    },
    {
        "id": 211,
        "level": "3",
        "text": "MTDog",
        "type": "Item",
        "items": null
    },
    {
        "id": 212,
        "level": "3",
        "text": "MTCat",
        "type": "Item",
        "items": null
    },
    {
        "id": 22,
        "level": "2",
        "text": "MChair",
        "type": "Item",
        "items": []
    },
    {
        "id": 221,
        "level": "3",
        "text": "MCDog",
        "type": "Item",
        "items": null
    },
    {
        "id": 222,
        "level": "3",
        "text": "MCCat",
        "type": "Item",
        "items": null
    },
    {
        "id": 3,
        "level": "1",
        "text": "Bruce",
        "type": "Folder",
        "items": []
    },
    {
        "id": 31,
        "level": "2",
        "text": "BTable",
        "type": "Item",
        "items": []
    },
    {
        "id": 311,
        "level": "3",
        "text": "BTDog",
        "type": "Item",
        "items": null
    },
    {
        "id": 312,
        "level": "3",
        "text": "BTCat",
        "type": "Item",
        "items": null
    },
    {
        "id": 32,
        "level": "2",
        "text": "Chair",
        "type": "Item",
        "items": []
    },
    {
        "id": 321,
        "level": "3",
        "text": "BCDog",
        "type": "Item",
        "items": null
    },
    {
        "id": 322,
        "level": "3",
        "text": "BCCat",
        "type": "Item",
        "items": null
    }
]
Share:
16,421

Related videos on Youtube

stack247
Author by

stack247

merge delete

Updated on June 22, 2022

Comments

  • stack247
    stack247 almost 2 years

    Data:

    var data = [
        {
          "id": 1,
          "level": "1",
          "text": "Sammy",
          "type": "Item",
          "items": [
            {
              "id": 11,
              "level": "2",
              "text": "Table",
              "type": "Item",
              "items": [
                {
                  "id": 111,
                  "level": "3",
                  "text": "Dog",
                  "type": "Item",
                  "items": null
                },
                {
                  "id": 112,
                  "level": "3",
                  "text": "Cat",
                  "type": "Item",
                  "items": null
                }
              ]
            },
            {
              "id": 12,
              "level": "2",
              "text": "Chair",
              "type": "Item",
              "items": [
                {
                  "id": 121,
                  "level": "3",
                  "text": "Dog",
                  "type": "Item",
                  "items": null
                },
                {
                  "id": 122,
                  "level": "3",
                  "text": "Cat",
                  "type": "Item",
                  "items": null
                }
              ]
            }
          ]
        },
        {
          "id": 2,
          "level": "1",
          "text": "Sundy",
          "type": "Item",
          "items": [
            {
              "id": 21,
              "level": "2",
              "text": "MTable",
              "type": "Item",
              "items": [
                {
                  "id": 211,
                  "level": "3",
                  "text": "MTDog",
                  "type": "Item",
                  "items": null
                },
                {
                  "id": 212,
                  "level": "3",
                  "text": "MTCat",
                  "type": "Item",
                  "items": null
                }
              ]
            },
            {
              "id": 22,
              "level": "2",
              "text": "MChair",
              "type": "Item",
              "items": [
                {
                  "id": 221,
                  "level": "3",
                  "text": "MCDog",
                  "type": "Item",
                  "items": null
                },
                {
                  "id": 222,
                  "level": "3",
                  "text": "MCCat",
                  "type": "Item",
                  "items": null
                }
              ]
            }
          ]
        },
        {
          "id": 3,
          "level": "1",
          "text": "Bruce",
          "type": "Folder",
          "items": [
            {
              "id": 31,
              "level": "2",
              "text": "BTable",
              "type": "Item",
              "items": [
                {
                  "id": 311,
                  "level": "3",
                  "text": "BTDog",
                  "type": "Item",
                  "items": null
                },
                {
                  "id": 312,
                  "level": "3",
                  "text": "BTCat",
                  "type": "Item",
                  "items": null
                }
              ]
            },
            {
              "id": 32,
              "level": "2",
              "text": "Chair",
              "type": "Item",
              "items": [
                {
                  "id": 321,
                  "level": "3",
                  "text": "BCDog",
                  "type": "Item",
                  "items": null
                },
                {
                  "id": 322,
                  "level": "3",
                  "text": "BCCat",
                  "type": "Item",
                  "items": null
                }
              ]
            }
          ]
        }
      ];
    

    Code:

    var fdr = [];
    var fd = function(n) {
      if (n.items) {
        _.forEach(n.items, function (value){
          fd(value);
        });
      }
    
      fdr.push(n);
    };
    _.forEach(data, fd);
    console.log(fdr);
    

    Desired output:

    var data = [
        {
          "id": 1,
          "level": "1",
          "text": "Sammy",
          "type": "Item",
          "items": []
        },
        {
          "id": 11,
          "level": "2",
          "text": "Table",
          "type": "Item",
          "items": []
        },
        {
          "id": 111,
          "level": "3",
          "text": "Dog",
          "type": "Item",
          "items": null
        },
        {
          "id": 112,
          "level": "3",
          "text": "Cat",
          "type": "Item",
          "items": null
        },
        {
          "id": 12,
          "level": "2",
          "text": "Chair",
          "type": "Item",
          "items": []
        },
        {
          "id": 121,
          "level": "3",
          "text": "Dog",
          "type": "Item",
          "items": null
        },
        {
          "id": 122,
          "level": "3",
          "text": "Cat",
          "type": "Item",
          "items": null
        },
        {
          "id": 2,
          "level": "1",
          "text": "Sundy",
          "type": "Item",
          "items": []
        },
        {
          "id": 21,
          "level": "2",
          "text": "MTable",
          "type": "Item",
          "items": []
        },
        {
          "id": 211,
          "level": "3",
          "text": "MTDog",
          "type": "Item",
          "items": null
        },
        {
          "id": 212,
          "level": "3",
          "text": "MTCat",
          "type": "Item",
          "items": null
        },
        {
          "id": 22,
          "level": "2",
          "text": "MChair",
          "type": "Item",
          "items": []
        },
        {
          "id": 221,
          "level": "3",
          "text": "MCDog",
          "type": "Item",
          "items": null
        },
        {
          "id": 222,
          "level": "3",
          "text": "MCCat",
          "type": "Item",
          "items": null
        },
        {
          "id": 3,
          "level": "1",
          "text": "Bruce",
          "type": "Folder",
          "items": []
        },
        {
          "id": 31,
          "level": "2",
          "text": "BTable",
          "type": "Item",
          "items": []
        },
        {
          "id": 311,
          "level": "3",
          "text": "BTDog",
          "type": "Item",
          "items": null
        },
        {
          "id": 312,
          "level": "3",
          "text": "BTCat",
          "type": "Item",
          "items": null
        },
        {
          "id": 32,
          "level": "2",
          "text": "Chair",
          "type": "Item",
          "items": []
        },
        {
          "id": 321,
          "level": "3",
          "text": "BCDog",
          "type": "Item",
          "items": null
        },
        {
          "id": 322,
          "level": "3",
          "text": "BCCat",
          "type": "Item",
          "items": null
        }
      ];
    

    Conditions:

    • Object have unknowns level. Some child item may have one level down and some could have up to 5.

    Questions

    The fd function in the code is what I have come up with. I believe there's a 'cleaner' way to do this, just can't think of something. Plus, the function return items object, render it circular object.

    JsBin: http://jsbin.com/debojiqove/2/edit?html,js,output

    Is there a way to flatten object recursively with lodash or just plain JavaScript?.

    • Nate Anderson
      Nate Anderson about 8 years
      are you wanting to remove the objects in the items arrays? I see in your example code you have empty arrays where there were objects.
    • stack247
      stack247 about 8 years
      Yes, pretty much. Preferably though, without modifying the original object, but it's not necessary.
    • Nick Zuber
      Nick Zuber about 8 years
      Lodash has a _.flattenDeep(array) method - is this what you're looking for?
    • stack247
      stack247 about 8 years
      @NickZuber Yeah, but I can't seem to get it to work for the outcome I want.
  • ryeballar
    ryeballar about 8 years
    You can add x = Object.create(x) before acc = acc.concat(x) statement, to avoid mutating the source array.
  • Роман Парадеев
    Роман Парадеев about 8 years
    It is a good idea to avoid mutations, but Object.create should not be used to clone an object. I would suggest to use x = Object.assign({}, x); instead.
  • stack247
    stack247 about 8 years
    I tried this, but can't seem to get it to work for the outcome I want.
  • stack247
    stack247 almost 8 years
    Please note that this approach requires ES6.
  • stack247
    stack247 almost 8 years
    It's actually a good idea to not mutilate Items and leave ItemId in the collection. Great work!
  • Firanolfind
    Firanolfind over 5 years
    best answer, should be top!
  • Raven
    Raven over 5 years
    posted a variant of this that is more reusable stackoverflow.com/a/53519360/58553