将递归数组对象转换为扁平化数组对象

3
我正在寻找一种将这个递归对象数组转换为扁平化对象数组以便更容易处理的方法。
[
  {
    "name": "bill",
    "car": "jaguar",
    "age": 30,
    "profiles": [
      {
        "name": "stacey",
        "car": "lambo",
        "age": 23,
        "profiles": [
          {
            "name": "martin",
            "car": "lexus",
            "age": 34,
            "profiles": []
          }
        ]
      }
    ]
  }
]

这是预期的输出。

[
  {
    "name": "bill",
    "car": "jaguar",
    "age": 30,
  },{
    "name": "stacey",
    "car": "lambo",
    "age": 23,
  },{
    "name": "martin",
    "car": "lexus",
    "age": 34,
  }
]

每个profiles数组可以有n个项目,这些项目可能有一个空的子profiles数组,也可能没有。注意转换后的数组对象不包含profiles
我可以使用underscorelodash来实现这一点。
7个回答

3

假设你的原始数据为o,我通过将Array.prototype.reduce与递归相结合得出了以下代码:

o.reduce(function recur(accumulator, curr) {
   var keys = Object.keys(curr);
   keys.splice(keys.indexOf('profiles'), 1);

   accumulator.push(keys.reduce(function (entry, key) {
       entry[key] = curr[key];
       return entry;
   }, {}));

   if (curr.profiles.length) {
       return accumulator.concat(curr.profiles.reduce(recur, []));
   }

   return accumulator;
}, []);

非常有趣,我以前从未见过reduce的使用 - Octav Zlatior
3
像reduce这样的高阶函数让我感到非常惊艳。 - axelduch
我希望有一种更通用的方法来应用变量属性,而不是像第二行那样逐个调用它们。 - ThomasReggi
@ThomasReggi 更新了一个更通用的解决方案。 - axelduch

1
const _ = require('lodash')

const arrayFromObject = (currentObject, currentArray) => {
  const {profiles, ...rest} = currentObject
  if (!_.isEmpty(currentObject.profiles)) {
    return arrayFromObject(currentObject.profiles!, [...currentArray, rest])
  }
  return [...currentArray, rest]
}

const flatArray = arrayFromObject(myRecursiveObject, [])

1
我会使用递归函数并将结果数组传递给它,以避免使用全局变量,代码如下:
var target = [];

var extractElements(source, target) {
    //TODO: check if source is array
    for (var i=0; i<source.length; i++) {
        // create a new element with our data
        var newElement = {
            name: source[i].name,
            car: source[i].car,
            age: source[i].age
        };
        // put it in our flattened array
        target.push(newElement);
        // check if we need to go deeper and pass our flattened array around
        if (source[i].profiles instanceof Array &&
            source[i].profiles.length>0)
            extractElements(source[i].profiles, target);
    }
}

console.log(target) // should list your elements nicely

我没有测试过它,所以仅供参考但需谨慎 :)
(编辑1:for循环中的“var i”)

0
将近三年过去了,仍在寻找一个适合所有情况的解决方案。这里提供一个方案,受到@axelduch答案的很大影响。
const {isPlainObject, isArray, get, omit, reduce} = require('lodash')
const recursiveFlatten = (tree, headProp, parentIdProp, parentRefProp, parent = {}) => {
  tree = isArray(tree) ? tree : [tree]
  return reduce(tree, (acq, current) => {
    const currentWithoutHead = omit(current, [headProp])
    if (parentIdProp && parentRefProp) currentWithoutHead[parentRefProp] = parent[parentIdProp] || null
    acq = [...acq, currentWithoutHead]
    const next = get(current, headProp)
    if (isPlainObject(next) || isArray(next)) {
      parent = currentWithoutHead
      acq = [...acq, ...recursiveFlatten(next, headProp, parentIdProp, parentRefProp, parent)]
    }
    return acq
  }, [])
}

这是一个简单的例子:

const example = recursiveFlatten({
  name: 'bill',
  love: true,
  lovers: [{
    name: 'jil',
    love: false,
    lovers: [{
      name: 'diana',
      love: false,
      lovers: false
    }, {
      name: 'einstein',
      love: false,
      lovers: {
        name: 'carl sagan',
        love: false,
        lovers: false
      }
    }]
  }]
}, 'lovers')

[ { name: 'bill', love: true },
  { name: 'jil', love: false },
  { name: 'diana', love: false },
  { name: 'einstein', love: false },
  { name: 'carl sagan', love: false } ]

这里是一个通过parentRef添加parentId属性的例子。
const example = recursiveFlatten({
  name: 'bill',
  love: true,
  lovers: [{
    name: 'jil',
    love: false,
    lovers: [{
      name: 'diana',
      love: false,
      lovers: false
    }, {
      name: 'einstein',
      love: false,
      lovers: {
        name: 'carl sagan',
        love: false,
        lovers: false
      }
    }]
  }]
}, 'lovers', 'name', 'parentName')

[ { name: 'bill', love: true, parentName: null },
  { name: 'jil', love: false, parentName: 'bill' },
  { name: 'diana', love: false, parentName: 'jil' },
  { name: 'einstein', love: false, parentName: 'jil' },
  { name: 'carl sagan', love: false, parentName: 'einstein' } ]

0

这是一个相当简单的技巧,可以解决最初定义的问题。

const recursiveFlatten = (tree) => 
  tree .length == 0
    ? []
    : tree .flatMap (({profiles = [], ... rest}) => [{... rest}, ... recursiveFlatten (profiles)])


const tree = [{name: "bill", car: "jaguar", age: 30, profiles: [{name: "stacey", car: "lambo", age: 23, profiles: [{name: "martin", car: "lexus", age: 34, profiles: []}]}]}, {name: "denise", car: "pinto", age: 28}]

console .log (
  recursiveFlatten (tree)
)

这个代码硬编码了名称“profiles”,并将其删除,保留生成的副本中其余属性不变。

您自己的答案提出了更复杂的要求。此版本通过几个可选参数处理这些要求,与您的答案一样,尽管调用方式在这里发生了变化,如果需要,可以轻松地进行修改:

const recursiveFlatten = (headProp, parentIdProp, parentRefProp, parent = {}) => (tree) => 
  tree .length == 0
    ? []
    : tree .flatMap (({[headProp]: children = [], ... rest}) => [
        {
          ... rest,
          ... (parentIdProp && parentRefProp ? {[parentRefProp]: parent[parentIdProp] || null} : {})
        },
        ... recursiveFlatten (headProp, parentIdProp, parentRefProp, rest) (children)
      ])
    

const tree = [{name: "bill", car: "jaguar", age: 30, profiles: [{name: "stacey", car: "lambo", age: 23, profiles: [{name: "martin", car: "lexus", age: 34, profiles: []}]}]}, {name: "denise", car: "pinto", age: 28}]

console .log (recursiveFlatten ('profiles') (tree))
console .log (recursiveFlatten ('profiles', 'name', 'parentName') (tree))

虽然如此,如果这个API出现在我的代码库中,我也不会感到很高兴。根据传递的参数数量而产生不同的行为会增加不必要的复杂性。我可能会将它们隐藏在一个API之下,例如

const recursiveFlatten = (parentIdProp, parentRefProp) => (headProp) => (tree) => ...

然后我们可以创建我们需要的函数,比如使用

const flattenProfiles = recursiveFlatten (null, null) ('profiles')

并且

const flattenAndExpand = recuriveFlatten ('name', 'parentName') ('profiles')

替换上面console.log()语句中的两个调用。


0

嗨,这也可以尝试一下...

    var out = [];
    var i=0;

    var extract = function(s, out) {
        if(s[0] == null){
            i = out.length -1;
            return false;
        }else { 
          out.push(s[0]);
        }
        extract(s[0].profiles, out);
        delete out[i--].profiles;
    };

    extract(a, out);  /// here 'a' is the input array and 'out' output
    console.log(out);

祝一切顺利...


如果根数组中有两个对象,并且第一个对象具有长度为0的“profiles”,则此操作不会产生预期行为。 - ThomasReggi
这也不会保留原始对象的状态,因为使用了 delete。 - ThomasReggi

0
var _ = require('lodash')
/**
 * Flatten a array-object via recursive property
 * @see {@link https://dev59.com/-43da4cB1Zd3GeqP4cNQ}
 * @param  {Array} arr                Array of objects with recursive props
 * @param  {String} recursiveProperty The string of the recursive property
 * @return {Array}                    Flat array of all recursive properties without recursive property
 */
function arrExtract (arr, recursiveProperty) {
  var extracted = []
  function _arrExtract (children) {
    _.each(children, function (item) {
      if (item[recursiveProperty] && item[recursiveProperty].length) _arrExtract(item[recursiveProperty])
      extracted.push(_.omit(item, recursiveProperty))
    })
  }
  _arrExtract(arr)
  return extracted
}

module.exports = arrExtract

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