【问题标题】:An elegant way to flatten an object一种扁平化对象的优雅方式
【发布时间】:2019-07-05 13:47:35
【问题描述】:

我面临一个简单的问题,就是用嵌套的对象来展平简单的对象。

尝试解决 SO,但它会引发错误:

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

// also tried these ones:

    console.log(Object.keys(weatherDetails).reduce((a, b, c) => {
        return Object.assign(a, {
            a: b
        })
    }, {})); 

// another one

let newWeather = Object.assign({}, (function() {
        var obj = {}
        for (var i = 0; i < Object.keys(weatherDetails).length; i++) {
            console.log(i, Object.keys(weatherDetails))
            obj[Object.keys(weatherDetails)] = weatherDetails[Object.keys(weatherDetails)]
        }
        return obj
    })())

这是我需要展平的对象,所以我们需要把它转过来:

{ 
    temperature: null, 
    humidity: null, 
    pressure: null, 
    windspeed: null, 
    pollution: {
        PM1: 1,
        PM10: 2,
        PM25: 3
    }
}

进入这个:

{ 
    temperature: null, 
    humidity: null, 
    pressure: null, 
    windspeed: null, 
    PM1: 1,
    PM10: 2,
    PM25: 3
}

【问题讨论】:

  • 在 lodash 中有一个简单的方法。也许你可以试试。
  • 能否还包括错误详细信息以及错误发生在哪一行?
  • @jlafay 没有错误,结果不是我所期望的,所以我做错了。哦,你的意思是 SO 那个,React 说:TypeError: can't convert null to object
  • 能否请您链接您获得 sn-ps 的答案并添加您得到的错误?
  • Object.assign({}, weather, { pollution: undefined }, weather.pollution)

标签: javascript arrays object reduce higher-order-functions


【解决方案1】:

假设您想要一个通用的解决方案,而不是使用静态键为您的 pollution 示例定制的解决方案,这里有一个快速的方法来实现它:

您只需遍历对象的属性键。如果属性是对象(我们称其为子对象),您需要将子对象的属性复制到主对象。

const obj = {
    temperature: null,
    humidity: null,
    pressure: null,
    windspeed: null,
    pollution: {
        PM1: 1,
        PM10: 2,
        PM25: 3
    }
};

function flatten(object) {
    for (const key in object) {
        if (!object.hasOwnProperty(key)) {
            continue;
        }

        if (typeof object[key] === 'object' && !Array.isArray(object[key]) && object[key] != null) {
            const childObject = object[key];
            delete object[key];
            object = {...object, ...childObject};
        }
    }
    return object;
}

console.log(flatten(obj));

【讨论】:

  • 此代码不正确,它在分配值条目后删除条目。如果输入键与值输入键之一相同,则会导致缺少属性!
【解决方案2】:

只是为了分享一种不同的方法(可能足够优雅),这是一个依赖函数生成器递归展平对象的解决方案。

由于它依赖于函数生成器,因此您最终可以动态构建对象并跳过不需要的键,因为结果是可迭代的。

以下示例有意使处理数组和null 值的过程稍微复杂一些,尽管原始问题中没有要求。

const original = { 
    temperature: null, 
    humidity: null, 
    pressure: null, 
    windspeed: null, 
    arrayKey: [1,2,3,'star!'],
    fnKey: function(i) {
      return i * 3;
    },
    pollution: {
        PM1: 1,
        PM10: 2,
        PM25: 3
    }
};
// Flattens an object.
function* flattenObject(obj, flattenArray = false) {
  // Loop each key -> value pair entry in the provided object.
  for (const [key, value] of Object.entries(obj)) {
    // If the target value is an object and it's not null (because typeof null is 'object'), procede.
    if (typeof(value) === 'object' && value !== null) {
      // if the targeted value is an array and arrays should be flattened, flatten the array.
      if (Array.isArray(value) && flattenArray) yield* flattenObject(value);
      // Otherwise, if the value is not an array, flatten it (it must be an object-like or object type).
      else if (!Array.isArray(value)) yield* flattenObject(value);
      // otherwise, just yield the key->value pair.
      else yield [key, value];
    }
    // otherwise, the value must be something which is not an object, hence, just yield it.
    else yield [key, value];
  }
}

// usage: assign to a new object all the flattened properties, using the spread operator (...) to assign the values progressively.
const res = Object.fromEntries(flattenObject(original));
console.log(res);
// sample usage by flattening arrays as well.
const res_flattened_arrays = Object.fromEntries(flattenObject(original, true));
console.log(res_flattened_arrays);
// custom object building by skipping a desired key
const resWithoutTemperature = {};
for (const [key, value] of flattenObject(original)) {
  if (key !== 'temperature') resWithoutTemperature[key] = value;
}
console.log(resWithoutTemperature);

【讨论】:

  • @JonasWilms 谢谢,上面已编辑。确实,这很聪明,我没想到fromEntries,确实让它变得更干净了!
【解决方案3】:

使用 Object.entries() 方法会更容易

您遍历对象的键和值,删除所有以对象为值的条目,并将该值的条目分配给对象。

let a = { 
    temperature: null, 
    humidity: null, 
    pressure: null, 
    windspeed: null, 
    pollution: {
        PM1: 1,
        PM10: 2,
        PM25: 3
    }
}

Object.entries(a).map(([key, value]) => {
    if(value && typeof value === 'object') {
         delete a[key];  // Delete entry
         Object.assign(a, value); // Add values from entry to object
    }
});

console.log(a)

一个班轮:

Object.entries(a).map(([key, value]) => value && typeof value === 'object' && delete a[key] && Object.assign(a, value));

这里还有一个不可变的函数方法:

Object.fromEntries(Object.entries(a).map(([key, value]) => 
    value && typeof value === 'object' ? 
         Object.entries(value) : [[key, value]]
).flat());

我个人更喜欢最后一种方法,因为它不会改变原始对象或任何对象。

【讨论】:

  • 我去看看,用我的手机打的^^°
  • 对象解构必须用括号括起来以与块区分开来。
  • 添加了一个班轮。
  • 添加了不可变函数方法和固定对象解构(应该是数组而不是对象)。
  • @mplungjan 进行了 sn-p 返回并添加了空检查。
【解决方案4】:

只需合并和删除属于 instanceof Object 的每个子属性。

let obj =
{ 
    temperature: null, 
    humidity: null, 
    pressure: null, 
    windspeed: null, 
    pollution: {
        PM1: 1,
        PM10: 2,
        PM25: 3,
		pollution: 4
    }
};

function flatten(obj)
{
	obj = Object.assign({}, obj);
	
	for (let i in obj)
		if (obj[i] instanceof Object)
		{
			obj = Object.assign(obj, obj[i]);

			// Prevent deletion of property i/"pollution", if it was not replaced by one of the child object's properties
			if (obj[i] === obj[i][i])
				delete obj[i];
		}
	
	return obj;
}

let obj_flattened = flatten(obj);
console.log(obj_flattened);

【讨论】:

  • @JonasWilms 关于数组,这也是我想到的第一件事,但是 cmets 中的 OP 只是说它们没有出现在示例中,所以显然不应该解决这个问题:|
  • @briosheje 正确。无论如何,数组在这种情况下都可以工作,因为它使用数组索引作为键
  • @briosheje Fur 肯定应该解决它。如果解决方案旨在通用,它应该适用于所有情况。
  • @JonasWilms 我同意你的观点,我个人会解决这个问题,但仍然...... :)......
【解决方案5】:

我通常使用 Lodash 进行这些类型的转换。 有了它,这样做非常简单。

查看以下代码示例:

const data = { 
    temperature: null, 
    humidity: null, 
    pressure: null, 
    windspeed: null, 
    pollution: {
        PM1: 1,
        PM10: 2,
        PM25: 3
    }
};

let flat = _.merge(data, data.pollution);
delete flat.pollution;
console.log(flat); // returns {"temperature":null,"humidity":null,"pressure":null,"windspeed":null,"PM1":1,"PM10":2,"PM25":3}

【讨论】:

  • 我会三思而后行地为您的应用程序添加一个包含大量其他代码和 4kb 的库,这是一种非常基本的方法。
  • 这是一个偏好问题。 Lodash 仍然是一个非常强大的解决方案,但在小型应用程序中可能有点矫枉过正
  • 此代码要求用户知道作为对象的确切属性,并且如前所述添加 lodash 是不必要的:_.merge 可以直接替换为 Object.assign
【解决方案6】:

试试这个(它会展平任何对象中包含的任何对象)遍历对象属性并确定属性是否是另一个要展平的对象并添加到“根”对象:

var o = { 
    temperature: null, 
    humidity: null, 
    pressure: null, 
    windspeed: null, 
    pollution: {
        PM1: 1,
        PM10: 2,
        PM25: 3,
        newobject:{
            a:1,
            b:2,
            c: {
                x:3,
                y:4,
                z:5                 
            }
        }
    }
}

    function flatten(obj){
        let retObj = {};
        let objConst = {}.constructor;
        for (el in obj){
            if(obj[el] !== null && obj[el].constructor === objConst){
                retObj = Object.assign({}, retObj, flatten(obj[el]));
            } else {
                retObj[el] = obj[el];
            }
        }
        return retObj;
    }

    console.log(flatten(o));

【讨论】:

  • 不需要objConst,它包含对Object的引用
  • 请按照 StackOverflow 指南中的说明为您的代码添加一些解释。
  • objConst 是属性是json对象的评估(在评估对象的属性是否不为空之后),这就是我把它放在那里的原因。
猜你喜欢
  • 2021-12-19
  • 1970-01-01
  • 1970-01-01
  • 2017-03-07
  • 1970-01-01
  • 2013-09-15
  • 2019-02-18
  • 1970-01-01
相关资源
最近更新 更多