一个用于压平嵌套对象的衬垫

One liner to flatten nested object

我需要展平嵌套对象。需要一个班轮。不确定此过程的正确术语是什么。 我可以使用纯 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));

这是我在常用库中的一个函数,正是出于这个目的。 我相信我是从一个类似的 Whosebug 问题中得到这个的,但不记得是哪个(编辑: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 字符串 class,如下所示:

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

给你:

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 的任何东西的解决方案。它使用下划线的 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;    
}

这里有适用于数组、基元、正则表达式、函数、任意数量的嵌套对象级别以及我可以抛给它们的几乎所有其他内容的原始解决方案。第一个以您期望 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));
});

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

简化可读示例,无依赖性

/**
 * 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
}

特征

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

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

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

对于打字稿 class 使用:

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

这里是,没有彻底测试。也使用 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);

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

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

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

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

一天快乐!

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

我用过递归

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

这是来自@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;
}

我的 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;
}

这是 TypeScript 中的 ES6 版本。它采用了此处和其他地方给出的最佳答案。部分功能:

  • 支持日期对象并将其转换为 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'
}

单行

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
}, {})

用法

const obj = {
  a:2,
  b: {
    c:3
  }
}

const output = crushObj(obj)
console.log(output)
// { a: 2, c: 3 }

这是一个只有 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));
<script src="https://underscorejs.org/underscore-umd-min.js"></script>

可读版本:

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));
<script src="https://underscorejs.org/underscore-umd-min.js"></script>

也适用于 Lodash。

const obj = {
  a:2,
  b: {
    c:3
  }
}
// recursive function for extracting keys
function extractKeys(obj) {
  let flattenedObj = {};
  for(let [key, value] of Object.entries(obj)){
    if(typeof value === "object") {
      flattenedObj =  {...flattenedObj, ...extractKeys(value)};
    } else {
      flattenedObj[key] = value;
    }
  }
  return flattenedObj;
}
 
//  main code
let flattenedObj = extractKeys(obj);
console.log(flattenedObj);