Mateusz Osuch
Mateusz Osuch

Reputation: 357

An elegant way to flatten an object

I am facing trival problem to flatten simple object with nested one inside.

Tried sollution fro SO, but it throws error:

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

Here is my object I need to flatten, so we need to turn this:

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

Into this:

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

Upvotes: 1

Views: 914

Answers (7)

Danish Aziz
Danish Aziz

Reputation: 483

Post ES6, you can use the below as an elegant and clean solution:

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

const { pollution, ...rest} = data;
const flattenedData = {...rest, ...pollution};

console.log(flattenedData)

Upvotes: 0

Mister Happy
Mister Happy

Reputation: 134

Just merge and delete every child property that's an 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);

Upvotes: 0

seahorsepip
seahorsepip

Reputation: 4819

This would be easier with the Object.entries() method

You loop over the object keys and values, delete all entries that have an object as value and assign the entries off that value to the object.

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)

One liner:

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

Also here's a immutable functional approach:

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

Personally I prefer this last approach since it doesn't mutate the original or any object.

Upvotes: 1

JC Hern&#225;ndez
JC Hern&#225;ndez

Reputation: 807

Try this (it will flatten any object contained in any object) iterating through the object attributes and identifying if an attribute is another object to flat and add to the "root" one:

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

Upvotes: 0

briosheje
briosheje

Reputation: 7446

Just to share a different approach (elegant enough, maybe), here is a solution relying on function generators to recursively flatten an object.

Because it relies on function generators, you can eventually build the object dynamically and skip undesired keys due to the fact the the result is iterable.

The below example has intentionally been made slightly more complex to handle arrays and null values as well, though not required in the original question.

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

Upvotes: 1

fjc
fjc

Reputation: 5825

Assuming you want to have a generic solution, not one that's custom-tailored to your pollution example with static keys, here's a quick way of achieving that:

You just iterate through your object's property keys. If a property is an object (let's call it child object), you'll copy your child object's properties to your main object.

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

Upvotes: 1

StefanN
StefanN

Reputation: 911

I usually use Lodash for these kinds of transformations. With it, it's very straightforward to do so.

Check out the following code sample:

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}

Upvotes: 0

Related Questions