递归深度展开JavaScript对象

27

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

代码:

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

期望的输出

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

条件:

  • 对象具有未知级别。某些子项 item 可能有一个级别向下,而有些可能高达 5 级。

问题

代码中的 fd 函数是我想出来的。我相信有一种更“清晰”的方法来做到这一点,只是想不到什么。此外,该函数返回 items 对象,将其呈现为循环对象。

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

有没有一种递归地使用 lodash 或普通 JavaScript 扁平化对象的方法?


1
你想要移除items数组中的对象吗?我在你的示例代码中看到了一些空数组,这些数组原本应该有对象。 - Nate Anderson
是的,基本上是这样。最好不要修改原始对象,但这并非必须。 - stack247
Lodash有一个_.flattenDeep(array)方法 - 这是你要找的吗? - Nick Zuber
@NickZuber 是的,但我似乎无法让它按照我想要的结果工作。 - stack247
16个回答

18

这是一个使用纯JavaScript编写的解决方案,与项目相关。它不会改变源数组。

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


2
不改变“Items”并在集合中保留“ItemId”实际上是一个好主意。干得好! - stack247

15

使用 _.flatMapDeep(自 Lodash 4.7 起可用):

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

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

你今天救了我一命,帮我完成了项目的截止日期。 - jnsnsml

15

加入一点ES6的味道。

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;
  }, []);
}

你可以在 acc = acc.concat(x) 语句之前添加 x = Object.create(x),以避免改变源数组。 - ryeballar
3
避免使用变异是一个好主意,但应该避免使用 Object.create 来克隆一个对象。 我建议使用 x = Object.assign({}, x); 代替。 - Роман Парадеев
我发布了一个更具可重用性的变体,链接为https://dev59.com/xloV5IYBdhLWcg3wA66x#53519360。 - Peter

5
使用reduce递归的简短解决方案。

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


3

纯 JavaScript

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 r = [];

function flatten(a) {
    if (a.length == 0) return;
    var o = {};
    o.id = a[0].id;
    o.level = a[0].level;
    o.text = a[0].text;
    o.type = a[0].type
    o.items = a[0].items == null ? null : []
    r.push(o);
    if (Array.isArray(a[0].items)) {
        flatten(a[0].items);
    }
    a.shift();
    flatten(a);
}

flatten(data);

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


3

我需要做同样的事情,在解决我的问题时,使用lodash找到了解决您问题的方法:

function kids(node) {
    return node.items
        ? [{...node, items: []}, _.map(node.items, kids)]
        : {...node, items: null};
}

_.flatMapDeep(data, kids);

最佳答案,应该排在首位! - Firanolfind

3

这里提供一种使用递归函数的解决方案,我称之为flattenNestedObjectsArray()(使用原生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));

console.log 输出:

[
    {
        "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
    }
]

2
只需要一个单行函数就可以完成这项工作。

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 }] }] }],

flatIron = (a,b) => a.reduce((p,c) => {!!c.items ? (p.push(c), flatIron(c.items,p), c.items = []) : p.push(c); return p},b),
 flatArr = flatIron(data,[]);

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


请注意,此方法需要ES6。 - stack247

2

这是我版本的递归flattenItems函数。 请注意,我在最终结果中删除了所有级别的items属性。

function flattenItems(data) {
    // flat is the array that we will return by the end
    var flat = [];
    data.forEach(function(item) {
        // get child properties only
        var flatItem = {};
        Object.keys(item).forEach(function(key) {
            if(item[key] && item.hasOwnProperty(key) && !Array.isArray(item[key])) {
                flatItem[key] = item[key];
            }
            // recursive flattern on subitems
            // add recursive call results to the 
            // current stack version of "flat", by merging arrays
            else if(Array.isArray(item[key])) {
                Array.prototype.push.apply(flat, flattenItems(item[key]));
            }
        });
        flat.push(flatItem);
    });
    // sort by level before returning
    return flat.sort(function(i1, i2) {
        return parseInt(i1.level) - parseInt(i2.level);
    });
  }

这是一个使用您提供的示例数据的 fiddle,请检查控制台。

2

使用递归规约函数的另一种方法

 _.reduce(data, function reducer(result, val) {
     var items = _.reduce(val.items, reducer, []);
     val.items = _.isArray(val.items) ? [] : val.items;
     return _.concat(result, val, items);
 }, []);

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