【问题标题】:Convert recursive array object to flat array object将递归数组对象转换为平面数组对象
【发布时间】:2015-08-05 10:29:05
【问题描述】:

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

[
  {
    "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 来实现这一目标。

【问题讨论】:

    标签: javascript arrays recursion underscore.js lodash


    【解决方案1】:

    我们将你的原始数据称为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
    • reduce 之类的高阶函数让我欲哭无泪
    • 我希望有一种更通用的方式来应用变量属性,而不是像第二行那样单独调用它们。
    • @ThomasReggi 更新了更通用的解决方案
    【解决方案2】:

    我会使用递归函数并在其中传递结果数组以避免使用全局变量,如下所示:

    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)

    【讨论】:

      【解决方案3】:
      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, [])
      

      【讨论】:

        【解决方案4】:

        你好,这个也可以试试……

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

        一切顺利...

        【讨论】:

        • 如果根数组中有两个对象并且第一个对象的 profiles 长度为 0,这不会产生预期的行为。
        • 这也不会保留原始对象的状态,因为删除。
        【解决方案5】:
        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
        

        【讨论】:

          【解决方案6】:

          差不多三年后,仍在为此寻找一刀切的解决方案。在这里,受到@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' } ]
          

          【讨论】:

            【解决方案7】:

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

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

            【讨论】:

              猜你喜欢
              • 2020-01-15
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2021-05-03
              • 2018-12-04
              • 2021-06-16
              相关资源
              最近更新 更多