简体   繁体   中英

Convert recursive array object to flat array object

I'm looking for a way to convert this array of recursive objects into a flat array of objects to make it easier to work with.

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

This is the expected output.

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

Each profiles array can have n amount of items, which may or may not have an empty array of sub profiles . Note the converted array objects don't contain profiles after the conversion.

I'm open to using underscore or lodash to achieve this.

Let's call your original data o , combining Array.prototype.reduce with recursion I came up with this:

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

I would use a recursive function and pass the resulting array in it to avoid working with globals, something in the lines of:

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

I haven't tested it, so use it for inspiration but beware :)

(edit1: "var i" in for)

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

Hi this can also be tried...

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

All the best...

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

Almost three years later and still looking for a one-size fits solution for this. Here it is, heavily influenced by @axelduch's answer.

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

Here's a simple example:

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

Here's an example adding parentId prop via 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' } ]

Here's a fairly simple technique that will solve the problem as originally defined.

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

This hard-codes the name "profiles" and removes it, keeping the rest of the properties intact in the copy generated.

Your own answer suggest substantially more complex requirements. This version handles these through several optional parameters, the way your answer does, although the way it's called changes here and could easily be altered if necessary:

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

I wouldn't be thrilled about this API in my own code-base, though. The differing behaviors depending on how many parameters are passed adds unnecessary complexity. I would probably bury them under an API such as

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

Then we could create functions we need, such as using

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

and

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

to replace the two call inside the console .log () statements above.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM