一行代码将嵌套对象扁平化

73

我需要将一个嵌套对象展平。需要一行代码解决。不确定这个过程的正确术语是什么。 我可以使用纯Javascript或库,我特别喜欢underscore。

我已经有了...

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

And I want ...

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

3
为什么这需要写在一行里? - user663031
4
老板不喜欢我编写自己的库函数,而我又不想让代码变得混乱。 - danday74
20个回答

89

给你:

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

1
值得注意的是,... 是 ES6 的语法,所以在旧版浏览器和旧版 nodejs 等上可能无法正常工作。 - Jite
21
不处理null或数组的情况。 - Eloff
1
你认为能否发布这个的TypeScript版本? - Zaf
@Eloff,你可以使用Array.isArray()方法为数组设置条件,并在其为真时跳过它。 - Hussam Khatib

75

简单易懂的示例,无需依赖

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

特点


34

这里有一行真正疯狂的代码,可以递归地扁平化嵌套对象:

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

快乐的一句话日!


非常感谢,我花了很多时间来寻找将嵌套对象展平的方法。您的解决方案完美地解决了我的问题。{"agama_id": "1", "jenis_kelamin": "1", "nama": "Emir", "nik": "3124125251", "no_handphone": "0822305152", "pekerjaan": "程序员", "tanggal_lahir": new Date(), "tempat_lahir": "Kediri"}我有一个对象,其中包含具有来自Date对象的日期值的属性的嵌套对象。但是其他解决方案似乎无法将其展平。但是您的答案对我很有帮助。我很好奇为什么日期无法展平。有人可以解释吗? - Galih indra
1
这是一个非常好的函数,如果有人想要更易读的版本,请访问以下链接:https://codesandbox.io/s/unruffled-engelbart-cdm1bm?file=/src/index.js - Alex McCabe

8

ES6原生,递归:

一行代码

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 crushed = crushObj(obj)
console.log(crushed)
// { a: 2, c: 3 }

同时提及代码如何满足要求。 - NitinSingh

6

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

此代码片段将不会在结果中保留中间键,因此“{ a: { b: { c: { d: {”将仅在结果中输出“{ d: ... }”,而不是“{ 'a.b.c.d': ... }”。 - Andrew

5

这是一个可以正确输出数组索引的平坦化函数。

function flatten(obj) {
  const result = {};
  for (const key of Object.keys(obj)) {
    if (typeof obj[key] === 'object') {
      const nested = flatten(obj[key]);
      for (const nestedKey of Object.keys(nested)) {
        result[`${key}.${nestedKey}`] = nested[nestedKey];
      }
    } else {
      result[key] = obj[key];
    }
  }
  return result;
}

示例输入:

{
  "first_name": "validations.required",
  "no_middle_name": "validations.required",
  "last_name": "validations.required",
  "dob": "validations.required",
  "citizenship": "validations.required",
  "citizenship_identity": {
    "name": "validations.required",
    "value": "validations.required"
  },
  "address": [
    {
      "country_code": "validations.required",
      "street": "validations.required",
      "city": "validations.required",
      "state": "validations.required",
      "zipcode": "validations.required",
      "start_date": "validations.required",
      "end_date": "validations.required"
    },
    {
      "country_code": "validations.required",
      "street": "validations.required",
      "city": "validations.required",
      "state": "validations.required",
      "zipcode": "validations.required",
      "start_date": "validations.required",
      "end_date": "validations.required"
    }
  ]
}

示例输出

const flattenedOutput = flatten(inputObj);

{
  "first_name": "validations.required",
  "no_middle_name": "validations.required",
  "last_name": "validations.required",
  "dob": "validations.required",
  "citizenship": "validations.required",
  "citizenship_identity.name": "validations.required",
  "citizenship_identity.value": "validations.required",
  "address.0.country_code": "validations.required",
  "address.0.street": "validations.required",
  "address.0.city": "validations.required",
  "address.0.state": "validations.required",
  "address.0.zipcode": "validations.required",
  "address.0.start_date": "validations.required",
  "address.0.end_date": "validations.required",
  "address.1.country_code": "validations.required",
  "address.1.street": "validations.required",
  "address.1.city": "validations.required",
  "address.1.state": "validations.required",
  "address.1.zipcode": "validations.required",
  "address.1.start_date": "validations.required",
  "address.1.end_date": "validations.required"
}

5

这不是一行代码的问题,但这里有一个解决方案,不需要使用ES6的任何东西。它使用underscore的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;    
}

5

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

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

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

在 TypeScript 类内部使用:

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

4
以下是 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'
}

3

以下是使用Underscore的实际一行代码,仅包含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。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接