【问题标题】:One liner to flatten nested object一个内衬来展平嵌套对象
【发布时间】:2016-01-07 07:08:12
【问题描述】:

我需要展平嵌套对象。需要一个班轮。不确定这个过程的正确术语是什么。 我可以使用纯 Javascript 或库,我特别喜欢下划线。

我有...

{
  a:2,
  b: {
    c:3
  }
}

我想要...

{
  a:2,
  c:3
}

我试过了……

var obj = {"fred":2,"jill":4,"obby":{"john":5}};
var resultObj = _.pick(obj, "fred")
alert(JSON.stringify(resultObj));

哪个有效,但我也需要这个才能工作......

var obj = {"fred":2,"jill":4,"obby":{"john":5}};
var resultObj = _.pick(obj, "john")
alert(JSON.stringify(resultObj));

【问题讨论】:

  • 为什么要排成一行?
  • 老板不喜欢我写自己的库函数,我不想乱码

标签: javascript underscore.js


【解决方案1】:

这是我在公共库中专门为此目的而提供的功能。 我相信我是从类似的 stackoverflow 问题中得到的,但不记得是哪个(编辑:Fastest way to flatten / un-flatten nested JSON objects - 谢谢 Yoshi!)

function flatten(data) {
    var result = {};
    function recurse (cur, prop) {
        if (Object(cur) !== cur) {
            result[prop] = cur;
        } else if (Array.isArray(cur)) {
             for(var i=0, l=cur.length; i<l; i++)
                 recurse(cur[i], prop + "[" + i + "]");
            if (l == 0)
                result[prop] = [];
        } else {
            var isEmpty = true;
            for (var p in cur) {
                isEmpty = false;
                recurse(cur[p], prop ? prop+"."+p : p);
            }
            if (isEmpty && prop)
                result[prop] = {};
        }
    }
    recurse(data, "");
    return result;
}

然后可以这样调用:

var myJSON = '{a:2, b:{c:3}}';
var myFlattenedJSON = flatten(myJSON);

您还可以将此函数附加到标准 Javascript 字符串类,如下所示:

String.prototype.flattenJSON = function() {
    var data = this;
    var result = {};
    function recurse (cur, prop) {
        if (Object(cur) !== cur) {
            result[prop] = cur;
        } else if (Array.isArray(cur)) {
             for(var i=0, l=cur.length; i<l; i++)
                 recurse(cur[i], prop + "[" + i + "]");
            if (l == 0)
                result[prop] = [];
        } else {
            var isEmpty = true;
            for (var p in cur) {
                isEmpty = false;
                recurse(cur[p], prop ? prop+"."+p : p);
            }
            if (isEmpty && prop)
                result[prop] = {};
        }
    }
    recurse(data, "");
    return result;
}

使用它,您可以执行以下操作:

var flattenedJSON = '{a:2, b:{c:3}}'.flattenJSON();

【讨论】:

  • 这是一个单行... var flattenedJSON = flatten(myUnflattenedJSON);
  • stackoverflow.com/q/19098797/697154 这个可能吗?也老实说,请不要回答这样的问题。它们对 SO 不利。
  • 看起来像那个!
【解决方案2】:

给你:

Object.assign({}, ...function _flatten(o) { return [].concat(...Object.keys(o).map(k => typeof o[k] === 'object' ? _flatten(o[k]) : ({[k]: o[k]})))}(yourObject))

总结:递归创建一个单一属性对象的数组,然后将它们全部与Object.assign结合起来。

这使用了 ES6 特性,包括 Object.assign 或扩展运算符,但它应该很容易重写而不需要它们。

对于那些不关心单行疯狂​​并希望能够实际阅读它的人(取决于您对可读性的定义):

Object.assign(
  {}, 
  ...function _flatten(o) { 
    return [].concat(...Object.keys(o)
      .map(k => 
        typeof o[k] === 'object' ?
          _flatten(o[k]) : 
          ({[k]: o[k]})
      )
    );
  }(yourObject)
)

【讨论】:

  • 值得注意的是,... 是 ES6,因此可能不适用于旧浏览器和旧版本的 nodejs 等。
  • 无法正确处理 null 或数组
【解决方案3】:

这不是一个单一的班轮,但这是一个不需要 ES6 任何东西的解决方案。它使用下划线的extend 方法,可以换成jQuery 的。

function flatten(obj) {
    var flattenedObj = {};
    Object.keys(obj).forEach(function(key){
        if (typeof obj[key] === 'object') {
            $.extend(flattenedObj, flatten(obj[key]));
        } else {
            flattenedObj[key] = obj[key];
        }
    });
    return flattenedObj;    
}

【讨论】:

    【解决方案4】:

    这里有适用于数组、原语、正则表达式、函数、任意数量的嵌套对象级别以及我可以扔给它们的几乎所有其他东西的普通解决方案。第一个以您期望 Object.assign 的方式覆盖属性值。

    ((o) => {
      return o !== Object(o) || Array.isArray(o) ? {}
        : Object.assign({}, ...function leaves(o) {
        return [].concat.apply([], Object.entries(o)
          .map(([k, v]) => {
            return (( !v || typeof v !== 'object'
                || !Object.keys(v).some(key => v.hasOwnProperty(key))
                || Array.isArray(v))
              ? {[k]: v}
              : leaves(v)
            );
          })
        );
      }(o))
    })(o)
    

    第二个将值累积到一个数组中。

    ((o) => {
      return o !== Object(o) || Array.isArray(o) ? {}
        : (function () {
          return Object.values((function leaves(o) {
            return [].concat.apply([], !o ? [] : Object.entries(o)
              .map(([k, v]) => {
                return (( !v || typeof v !== 'object'
                    || !Object.keys(v).some(k => v.hasOwnProperty(k))
                    || (Array.isArray(v) && !v.some(el => typeof el === 'object')))
                  ? {[k]: v}
                  : leaves(v)
                );
              })
            );
          }(o))).reduce((acc, cur) => {
            return ((key) => {
              acc[key] = !acc[key] ? [cur[key]]
                : new Array(...new Set(acc[key].concat([cur[key]])))
            })(Object.keys(cur)[0]) ? acc : acc
          }, {})
        })(o);
    })(o)
    

    另外请不要在生产中包含这样的代码,因为它非常难以调试。

    function leaves1(o) {
      return ((o) => {
        return o !== Object(o) || Array.isArray(o) ? {}
          : Object.assign({}, ...function leaves(o) {
          return [].concat.apply([], Object.entries(o)
            .map(([k, v]) => {
              return (( !v || typeof v !== 'object'
                  || !Object.keys(v).some(key => v.hasOwnProperty(key))
                  || Array.isArray(v))
                ? {[k]: v}
                : leaves(v)
              );
            })
          );
        }(o))
      })(o);
    }
    
    function leaves2(o) {
      return ((o) => {
        return o !== Object(o) || Array.isArray(o) ? {}
          : (function () {
            return Object.values((function leaves(o) {
              return [].concat.apply([], !o ? [] : Object.entries(o)
                .map(([k, v]) => {
                  return (( !v || typeof v !== 'object'
                      || !Object.keys(v).some(k => v.hasOwnProperty(k))
                      || (Array.isArray(v) && !v.some(el => typeof el === 'object')))
                    ? {[k]: v}
                    : leaves(v)
                  );
                })
              );
            }(o))).reduce((acc, cur) => {
              return ((key) => {
                acc[key] = !acc[key] ? [cur[key]]
                  : new Array(...new Set(acc[key].concat([cur[key]])))
              })(Object.keys(cur)[0]) ? acc : acc
            }, {})
          })(o);
      })(o);
    }
    
    const obj = {
      l1k0: 'foo',
      l1k1: {
        l2k0: 'bar',
        l2k1: {
          l3k0: {},
          l3k1: null
        },
        l2k2: undefined
      },
      l1k2: 0,
      l2k3: {
        l3k2: true,
        l3k3: {
          l4k0: [1,2,3],
          l4k1: [4,5,'six', {7: 'eight'}],
          l4k2: {
            null: 'test',
            [{}]: 'obj',
            [Array.prototype.map]: Array.prototype.map,
            l5k3: ((o) => (typeof o === 'object'))(this.obj),
          }
        }
      },
      l1k4: '',
      l1k5: new RegExp(/[\s\t]+/g),
      l1k6: function(o) { return o.reduce((a,b) => a+b)},
      false: [],
    }
    const objs = [null, undefined, {}, [], ['non', 'empty'], 42, /[\s\t]+/g, obj];
    
    objs.forEach(o => {
      console.log(leaves1(o));
    });
    objs.forEach(o => {
      console.log(leaves2(o));
    });

    【讨论】:

      【解决方案5】:
      function flatten(obj: any) {
        return Object.keys(obj).reduce((acc, current) => {
          const key = `${current}`;
          const currentValue = obj[current];
          if (Array.isArray(currentValue) || Object(currentValue) === currentValue) {
            Object.assign(acc, flatten(currentValue));
          } else {
            acc[key] = currentValue;
          }
          return acc;
        }, {});
      };
      
      let obj = {
        a:2,
        b: {
          c:3
        }
      }
      
      console.log(flatten(obj))
      

      演示 https://stackblitz.com/edit/typescript-flatten-json

      【讨论】:

        【解决方案6】:

        简化可读示例,无依赖关系

        /**
         * Flatten a multidimensional object
         *
         * For example:
         *   flattenObject{ a: 1, b: { c: 2 } }
         * Returns:
         *   { a: 1, c: 2}
         */
        export const flattenObject = (obj) => {
          const flattened = {}
        
          Object.keys(obj).forEach((key) => {
            const value = obj[key]
        
            if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
              Object.assign(flattened, flattenObject(value))
            } else {
              flattened[key] = value
            }
          })
        
          return flattened
        }
        
        

        特点

        【讨论】:

          【解决方案7】:

          我喜欢这段代码,因为它更容易理解。

          编辑:我添加了一些我需要的功能,所以现在有点难以理解。

          const data = {
            a: "a",
            b: {
              c: "c",
              d: {
                e: "e",
                f: [
                  "g",
                  {
                    i: "i",
                    j: {},
                    k: []
                  }
                ]
              }
            }
          };
          
          function flatten(data, response = {}, flatKey = "", onlyLastKey = false) {
            for (const [key, value] of Object.entries(data)) {
              let newFlatKey;
              if (!isNaN(parseInt(key)) && flatKey.includes("[]")) {
                newFlatKey = (flatKey.charAt(flatKey.length - 1) == "." ? flatKey.slice(0, -1) : flatKey) + `[${key}]`;
              } else if (!flatKey.includes(".") && flatKey.length > 0) {
                newFlatKey = `${flatKey}.${key}`;
              } else {
                newFlatKey = `${flatKey}${key}`;
              }
              if (typeof value === "object" && value !== null && Object.keys(value).length > 0) {
                flatten(value, response, `${newFlatKey}.`, onlyLastKey);
              } else {
                if(onlyLastKey){
                  newFlatKey = newFlatKey.split(".").pop();
                }
                if (Array.isArray(response)) {
                  response.push({
                    [newFlatKey.replace("[]", "")]: value
                  });
                } else {
                  response[newFlatKey.replace("[]", "")] = value;
                }
              }
            }
            return response;
          }
          
          console.log(flatten(data));
          console.log(flatten(data, {}, "data"));
          console.log(flatten(data, {}, "data[]"));
          console.log(flatten(data, {}, "data", true));
          console.log(flatten(data, {}, "data[]", true));
          console.log(flatten(data, []));
          console.log(flatten(data, [], "data"));
          console.log(flatten(data, [], "data[]"));
          console.log(flatten(data, [], "data", true));
          console.log(flatten(data, [], "data[]", true));

          演示https://stackblitz.com/edit/typescript-flatter

          对于打字稿类使用:

          function flatten(data: any, response = {}, flatKey = "", onlyLastKey = false) {
            for (const [key, value] of Object.entries(data)) {
              let newFlatKey: string;
              if (!isNaN(parseInt(key)) && flatKey.includes("[]")) {
                newFlatKey = (flatKey.charAt(flatKey.length - 1) == "." ? flatKey.slice(0, -1) : flatKey) + `[${key}]`;
              } else if (!flatKey.includes(".") && flatKey.length > 0) {
                newFlatKey = `${flatKey}.${key}`;
              } else {
                newFlatKey = `${flatKey}${key}`;
              }
              if (typeof value === "object" && value !== null && Object.keys(value).length > 0) {
                flatten(value, response, `${newFlatKey}.`, onlyLastKey);
              } else {
                if(onlyLastKey){
                  newFlatKey = newFlatKey.split(".").pop();
                }
                if (Array.isArray(response)) {
                  response.push({
                    [newFlatKey.replace("[]", "")]: value
                  });
                } else {
                  response[newFlatKey.replace("[]", "")] = value;
                }
              }
            }
            return response;
          }
          

          【讨论】:

            【解决方案8】:

            就这样,没有经过彻底测试。也使用 ES6 语法!!

            loopValues(val){
            let vals = Object.values(val);
            let q = [];
            vals.forEach(elm => {
              if(elm === null || elm === undefined) { return; }
                if (typeof elm === 'object') {
                  q = [...q, ...this.loopValues(elm)];
                }
                return q.push(elm);
              });
              return q;
            }
            
            let flatValues = this.loopValues(object)
            flatValues = flatValues.filter(elm => typeof elm !== 'object');
            console.log(flatValues);
            

            【讨论】:

            • 我刚刚更新了它以考虑未定义和空值!
            • 这将返回一个数组,而不是一个对象
            【解决方案9】:

            仅展平对象的第一级并将重复的对象键合并到一个数组中:

            var myObj = {
              id: '123',
              props: {
                Name: 'Apple',
                Type: 'Fruit',
                Link: 'apple.com',
                id: '345'
              },
              moreprops: {
                id: "466"
              }
            };
            
            const flattenObject = (obj) => {
              let flat = {};
              for (const [key, value] of Object.entries(obj)) {
                if (typeof value === 'object' && value !== null) {
                  for (const [subkey, subvalue] of Object.entries(value)) {
                    // avoid overwriting duplicate keys: merge instead into array
                    typeof flat[subkey] === 'undefined' ?
                      flat[subkey] = subvalue :
                      Array.isArray(flat[subkey]) ?
                        flat[subkey].push(subvalue) :
                        flat[subkey] = [flat[subkey], subvalue]
                  }
                } else {
                  flat = {...flat, ...{[key]: value}};
                }
              }
              return flat;
            }
            
            console.log(flattenObject(myObj))

            【讨论】:

              【解决方案10】:

              这是一个真正的、疯狂的单行,它递归地扁平化嵌套对象:

              const flatten = (obj, roots=[], sep='.') => Object.keys(obj).reduce((memo, prop) => Object.assign({}, memo, Object.prototype.toString.call(obj[prop]) === '[object Object]' ? flatten(obj[prop], roots.concat([prop]), sep) : {[roots.concat([prop]).join(sep)]: obj[prop]}), {})
              

              多行版本,解释:

              // $roots keeps previous parent properties as they will be added as a prefix for each prop.
              // $sep is just a preference if you want to seperate nested paths other than dot.
              const flatten = (obj, roots = [], sep = '.') => Object
                // find props of given object
                .keys(obj)
                // return an object by iterating props
                .reduce((memo, prop) => Object.assign(
                  // create a new object
                  {},
                  // include previously returned object
                  memo,
                  Object.prototype.toString.call(obj[prop]) === '[object Object]'
                    // keep working if value is an object
                    ? flatten(obj[prop], roots.concat([prop]), sep)
                    // include current prop and value and prefix prop with the roots
                    : {[roots.concat([prop]).join(sep)]: obj[prop]}
                ), {})
              

              一个例子:

              const obj = {a: 1, b: 'b', d: {dd: 'Y'}, e: {f: {g: 'g'}}}
              const flat = flatten(obj)
              {
                'a': 1, 
                'b': 'b', 
                'd.dd': 'Y', 
                'e.f.g': 'g'
              }
              

              一天快乐!

              【讨论】:

              • 您没有在递归 flatten 调用中传递 sep 参数,因此它使用默认值 (.)。
              • @JoelB 修复了这个错误!
              【解决方案11】:

              我知道它已经很长了,但它可能对将来的某些人有所帮助

              我用过递归

              let resObj = {};
              function flattenObj(obj) {
                  for (let key in obj) {
                      if (!(typeof obj[key] == 'object')) {
                          // console.log('not an object', key);
                          resObj[key] = obj[key];
                          // console.log('res obj is ', resObj);
                      } else {
                          flattenObj(obj[key]);
                      }
                  }
              
                  return resObj;
              }
              

              【讨论】:

                【解决方案12】:

                这是来自@Webber 答案的我的 TypeScript 扩展。还支持日期:

                private flattenObject(obj: any): any {
                  const flattened = {};
                
                  for (const key of Object.keys(obj)) {
                    if (isNullOrUndefined(obj[key])) {
                      continue;
                    }
                
                    if (typeof obj[key].getMonth === 'function') {
                      flattened[key] = (obj[key] as Date).toISOString();
                    } else if (typeof obj[key] === 'object' && obj[key] !== null) {
                      Object.assign(flattened, this.flattenObject(obj[key]));
                    } else {
                      flattened[key] = obj[key];
                    }
                  }
                
                  return flattened;
                }
                

                【讨论】:

                  【解决方案13】:

                  我的 ES6 版本:

                  const flatten = (obj) => {
                      let res = {};
                      for (const [key, value] of Object.entries(obj)) {
                          if (typeof value === 'object') {
                              res = { ...res, ...flatten(value) };
                          } else {
                              res[key] = value;
                          }
                      }
                      return res;
                  }
                  

                  【讨论】:

                    【解决方案14】:

                    这是 TypeScript 中的 ES6 版本。它需要这里和其他地方给出的最佳答案。一些特点:

                    • 支持 Date 对象并将其转换为 ISO 字符串
                    • 在父键和子键之间添加下划线(例如,{a: {b: 'test'}} 变为 {a_b: 'test'}
                    const flatten = (obj: Record<string, unknown>, parent?: string): Record<string, unknown> => {
                        let res: Record<string, unknown> = {}
                    
                        for (const [key, value] of Object.entries(obj)) {
                            const propName = parent ? parent + '_' + key : key
                            const flattened: Record<string, unknown> = {}
                    
                            if (value instanceof Date) {
                                flattened[key] = value.toISOString()
                            } else if(typeof value === 'object' && value !== null){
                                res = {...res, ...flatten(value as Record<string, unknown>, propName)}
                            } else {
                                res[propName] = value
                            }
                        }
                    
                        return res
                    }
                    

                    一个例子:

                    const example = {
                        person: {
                            firstName: 'Demo',
                            lastName: 'Person'
                        },
                        date: new Date(),
                        hello: 'world'
                    }
                    
                    // becomes
                    
                    const flattenedExample = {
                        person_firstName: 'Demo',
                        person_lastName: 'Person',
                        date: '2021-10-18T10:41:14.278Z',
                        hello: 'world'
                    }
                    

                    【讨论】:

                      【解决方案15】:

                      单线

                      const crushObj = (obj) => Object.keys(obj).reduce((acc, cur) => typeof obj[cur] === 'object' ? { ...acc, ...crushObj(obj[cur]) } : { ...acc, [cur]: obj[cur] } , {})
                      

                      扩展

                      const crushObj = (obj = {}) => Object.keys(obj || {}).reduce((acc, cur) => {
                        if (typeof obj[cur] === 'object') {
                          acc = { ...acc, ...crushObj(obj[cur])}
                        } else { acc[cur] = obj[cur] }
                        return acc
                      }, {})
                      

                      【讨论】:

                      • 还提到代码如何满足需求
                      【解决方案16】:

                      这是一个只有 91 个字符的实际单行,使用下划线。 (当然。还有什么?)

                      var { reduce, isObject } = _;
                      
                      var data = {
                          a: 1,
                          b: 2,
                          c: {
                              d: 3,
                              e: 4,
                              f: {
                                  g: 5
                              },
                              h: 6
                          }
                      };
                      
                      var tip = (v, m={}) => reduce(v, (m, v, k) => isObject(v) ? tip(v, m) : {...m, [k]: v}, m);
                      
                      console.log(tip(data));
                      &lt;script src="https://underscorejs.org/underscore-umd-min.js"&gt;&lt;/script&gt;

                      可读版本:

                      var { reduce, isObject, extend } = _;
                      
                      var data = {
                          a: 1,
                          b: 2,
                          c: {
                              d: 3,
                              e: 4,
                              f: {
                                  g: 5
                              },
                              h: 6
                          }
                      };
                      
                      // This function is passed to _.reduce below.
                      // We visit a single key of the input object. If the value
                      // itself is an object, we recursively copy its keys into
                      // the output object (memo) by calling tip. Otherwise we
                      // add the key-value pair to the output object directly.
                      function tipIteratee(memo, value, key) {
                          if (isObject(value)) return tip(value, memo);
                          return extend(memo, {[key]: value});
                      }
                      
                      // The entry point of the algorithm. Walks over the keys of
                      // an object using _.reduce, collecting all tip keys in memo.
                      function tip(value, memo = {}) {
                          return _.reduce(value, tipIteratee, memo);
                      }
                      
                      console.log(tip(data));
                      &lt;script src="https://underscorejs.org/underscore-umd-min.js"&gt;&lt;/script&gt;

                      也适用于 Lodash。

                      【讨论】:

                        猜你喜欢
                        • 2018-10-24
                        • 2017-07-02
                        • 2020-12-22
                        • 2021-10-19
                        • 2020-06-23
                        • 2012-05-29
                        • 2017-10-19
                        • 2019-04-14
                        相关资源
                        最近更新 更多