将递归数组对象转换为平面数组对象

时间:2015-08-05 10:29:06

标签: javascript arrays recursion underscore.js lodash

我正在寻找一种方法将这个递归对象数组转换为平面对象数组,以便更容易使用。

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

答案 0 :(得分: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;
}, []);

答案 1 :(得分: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

我还没有测试过,所以请用它来获取灵感,但要注意:)

(edit1:“var i”in for)

答案 2 :(得分: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, [])

答案 3 :(得分: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);

一切顺利......

答案 4 :(得分:0)

var _ = require('lodash')
/**
 * Flatten a array-object via recursive property
 * @see {@link http://stackoverflow.com/questions/31829897/convert-recursive-array-object-to-flat-array-object}
 * @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

答案 5 :(得分: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 } ]

以下是通过parentId添加parentRef道具的示例。

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

答案 6 :(得分: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)
)

这会将名称“配置文件”硬编码并删除,从而使其余属性在生成的副本中保持完整。

您自己的回答表明要复杂得多的要求。此版本通过几个可选参数来处理这些问题,就像您的答案一样,尽管此处称呼的方式已更改,并且在必要时可以轻松更改:

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 ()语句中的两个调用。