递归深度展开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个回答

2

修改了Роман Парадеев的答案,使其更具有动态性。

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

现在items不再是硬编码的,你可以使用flatten(data, x => x.items)来操作它。

1
自从 Lo-Dash 3.0.0 版本,_.flattenDeep(data) 将按您所需深度展平数组。 还有一个用于浅层展开的 _.flatten(data) 函数。

我尝试过这个,但似乎无法得到我想要的结果。 - stack247

1

既然这个旧问题被重新提出,那么这里有一个现代化的版本。它不会改变(@Nina Scholz: 或者说“毁坏”!)原始数据,而是返回一个包含新对象的新数组。

const flattenItems = (xs) =>
  xs. flatMap (({items, ... node}) => [node, ... flattenItems (items || [])])

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

console .log (flattenItems (data))
.as-console-wrapper {max-height: 100% !important; top: 0}

这个版本不包括相当无用的items属性。很容易添加它,就像这样:
const flattenItems = (xs) =>
  xs. flatMap (({items, ... node}) => [
    {... node, items: items == null ? null : []}, 
    ... flattenItems (items || [])
  ])

0

这里有另一种使用object-scan的解决方案。该库是围绕遍历对象层次结构设计的,因此如果这不是确切所需的结果,则提供了很大的灵活性。

.as-console-wrapper {max-height: 100% !important; top: 0}
<script type="module">
import objectScan from 'https://cdn.jsdelivr.net/npm/object-scan@18.4.0/lib/index.min.js';

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

const fn = objectScan(['[*].**{items[*]}'], {
  rtn: ({ value }) => ({ ...value, items: value.items === null ? null : [] }),
  afterFn: ({ result }) => result.reverse()
});

const r = fn(data);
console.log(r);
/* => [
  { 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 }
] */
</script>

免责声明:本人是object-scan的作者。


0

使用 map 和 reduce 很简单:

export function flatten<T>(value: T): T[] {
  const out: T[] = [];
  if (value.children) {
    const children = Object.values(value.children);
    out.push(
      value,
      ...children.map(flatten).reduce((acc, curVal) => {
        return acc.concat(curVal);
      }),
    );
  } else {
    out.push(value);
  }
  return out;
}

0

这个解决方案应该适用于IE11; 使用了filter、map和reduce。

var item = function(x) {
    return {
        "id": x.id,
        "level": x.level,
        "text": x.text,
        "type": x.type,
        "items": x.items ? [] : null
    }
}

var flatten = function(a, b) {
    return a.concat(b);
};

var onlyUnique = function(acc, curr) {
    if (acc.length == 0) {
        acc.push(curr);
    } else {
        var search = acc.filter(function(x){return x.id===curr.id;})
        if (search.length == 0) {
            acc.push(curr);
        }
    }
    return acc;
}

var newData = data.map(function(x) {

    return x.items.map(function(xx) {
        return xx.items.map(function(xxx) {
            return [item(x), item(xx), item(xxx)];
        }).reduce(flatten, []);


    }).reduce(flatten, [])



}).reduce(flatten, []).reduce(onlyUnique, []);;

console.log(JSON.stringify(newData, null, 2))

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