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

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个回答

3
这是我在通用库中为了这个目的而拥有的一个函数。 我相信我是从类似的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();

1
这是一行代码...var flattenedJSON = flatten(myUnflattenedJSON); - Sk93
1
这个?https://dev59.com/92Ik5IYBdhLWcg3wn_f1 说实话,请不要回答这样的问题。它们对SO来说是不好的。 - Yoshi
那看起来就是那个! - Sk93

3

以下是适用于数组、基本类型、正则表达式、函数、任意嵌套对象级别以及几乎所有我能想到的其他内容的原始解决方案。第一个解决方案覆盖属性值的方式与 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));
});


2

如果只需要将对象的第一层展开并将重复的键合并成数组:

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


2

Object.assign需要使用polyfill。这个版本与以前的版本类似,但它不使用Object.assign,并且仍然跟踪父级名称。

const flatten = (obj, parent = null) => Object.keys(obj).reduce((acc, cur) => 
    typeof obj[cur] === 'object' ? { ...acc, ...flatten(obj[cur], cur) } :
    { ...acc, [((parent) ? parent + '.' : "") + cur]: obj[cur] } , {})

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

const flattened = flatten(obj)
console.log(flattened)

这将不会在结果中保留根键,所以 { a: [ { b, c } ] } 在结果中只会输出 { 0.b: ..., 0.c: ... } 而不是 { 'a.0.b': ..., 'a.0.c': ... } - Andrew

1

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

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

Demo https://stackblitz.com/edit/typescript-flatten-json


1
我知道已经过了很长时间,但对于将来的某个人可能会有所帮助。
我使用了递归。
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;
}

1

以下内容尚未经过充分测试。还使用了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);

我刚刚更新了它,以考虑未定义和空值! - Ikenna Anthony Okafor
这会返回一个数组,而不是一个对象。 - Marco Lackovic

1

这是一个简单的 TypeScript 函数,可以将对象扁平化连接键:

function crushObj(
    rootObj:{[key: string]: any},
    obj: any,
    split = '/',
    prefix = ''
) {
    if (typeof obj === 'object') {
        for (const key of Object.keys(obj)) {
            const val = obj[key];
            delete obj[key];

            const rootKey = prefix.length > 0 ? `${prefix}${split}${key}` : key;

            crushObj(rootObj, val, split, rootKey)
        }
    }
    else {
        rootObj[prefix] = obj;
    }
}

你可以像这样使用它:

const obj = {
    name: 'John',
    address: {
      street: 'Aldo',
      number: 12,
    }
}
crushObj(obj, obj);

结果:

{
    name: "John",
    "address/street": "Aldo",
    "address/number": 12
}

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

这与Marco Lackovic在此问题上的另一个答案相同。 - Besworks

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