how to flatten a nested object using javascript - javascript

var obj = {
a: {
aa: {
aaa: {
aaaa: "a"
}
}
},
b: {
bb: {
bbb: "b"
}
}
}
flatten(obj)
//=>[["a","b"],["aa","bb"],["aaa","bbb"],["aaaa"]]
This is interesting question,my friend says BFS or DFS can be able to solve the problem,but I can't

you can use recursion and keep a level counter, on each step of recursion add keys to the result array. For this you have to check if an array already exists at that level then concatenate to that array.
var obj = {
a: {
aa: {
aaa: {
aaaa: "a"
}
}
},
b: {
bb: {
bbb: "b"
}
}
}
var result = [];
function flatten(obj, level){
var keys = Object.keys(obj);
result[level] = result[level] !== undefined ? result[level].concat(keys) : keys;
keys.forEach(x => typeof obj[x] === 'object' && flatten(obj[x], level + 1));
}
flatten(obj, 0);
console.log(result);

Related

Iterate through an array with a single index that contains multiple objects

I have some data that looks like this
let arr = [
{
a:1,
b:2,
c:3
},
{
a:4,
b:5,
c:6
},
{
a:7,
b:8,
c:9
}
]
and I'd like to get it to reformat like this
{
a: [1,4,7],
b: [2,5,8],
c: [3,6,9]
}
Here is my solution:
let arr = [
{
a:1,
b:2,
c:3
},
{
a:4,
b:5,
c:6
},
{
a:7,
b:8,
c:9
}
]
// {
// a: [1,4,7],
// b: [2,5,8],
// c: [3,6,9]
// }
function practice (arr) {
console.log(typeof arr) // WHY IS THIS RETURNING AN OBJECT???
let keys = Object.keys(arr[0])
const resultObj = {}
for (let key of keys) {
resultObj[key] = []
}
arr.forEach((x,idx)=> {
for (let key in x) {
resultObj[key].push(x[key])
}
})
return resultObj
}
practice(arr)
I know that my solution is not the most efficient method. While I completed the exercise, I am having trouble understanding the concepts below:
At first glance, arr to me seems like an array with a single index
containing three objects. For example, arr[0] = {obj1},{obj2},{obj3}, but
I performed a typeof check on arr and it returned object.
When I console log arr at a specified index arr[1], it prints out {a:4,b:5,c:6} as if it is an array.
My question is what is happening here and what exactly is this type of data structure?
Please offer me a more clean and efficient code to this question and explain to me the concepts.
Try
function practice (arr) {
let resultObj = {};
arr.forEach((x) => {
for (let key in x) {
if (resultObj.hasOwnProperty(key)) {
resultObj[key].push(x[key]);
} else {
resultObj[key] = [x[key]];
}
}
});
return resultObj;
}
In order to check for an array, you should make use of Array.isArray() method. typeof will give you an object since Array is essentially a form of object in javascript created using the Object constructor.
To get a desired output, all you need to do is to loop over the array and store the values in an object
let arr = [
{
a:1,
b:2,
c:3
},
{
a:4,
b:5,
c:6
},
{
a:7,
b:8,
c:9
}
]
var res = {};
arr.forEach((obj) => {
Object.entries(obj).forEach(([key, val]) => {
if(res[key]) {
res[key].push(val);
} else {
res[key] = [val];
}
})
});
console.log(res);
You may use array reduce() method for this like:
Loop through all the keys of each object in the array using Object.keys(o) inside the reduce() method
Inside the loop, initialize the accumulator with the same key as we have inside the loop and the initial value of that key as empty array [].
Then using r[k].push(o[k]), we are adding the matching key values inside this array.
Then finally return the object r from the .reduce() method.
let arr = [{a:1,b:2,c:3},{a:4,b:5,c:6},{a:7,b:8,c:9}];
const res = arr.reduce((r, o) => {
Object.keys(o).forEach((k) => {
r[k] = r[k] || [];
r[k].push(o[k])
});
return r;
}, {})
console.log(res)
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can try this-
let arr = [
{
a:1,
b:2,
c:3
},
{
a:4,
b:5,
c:6
},
{
a:7,
b:8,
c:9
}
];
let res = {};
Object.values(arr).forEach(value => {
Object.keys(value).forEach(key => {
if (typeof res[key] === 'undefined') {
res[key] = [];
}
res[key].push(value[key])
})
})
console.log(res);
With multiple forEach loops build an Object with the keys and value are merged for same key.
let arr = [
{
a: 1,
b: 2,
c: 3
},
{
a: 4,
b: 5,
c: 6
},
{
a: 7,
b: 8,
c: 9
}
];
const res = {};
arr.forEach(item => {
Object.keys(item).forEach(
key => (res[key] = key in res ? [...res[key], item[key]] : [item[key]])
);
});
console.log(res);

How to merge array of objects in one object, including inner objects - JavaScript?

Suppose I have the following array of objects:
var list = [
{ a: 1,
b: { c: 'x', k: []}
},
{ a: 1,
b: {c: 'x', d: 8}
}
];
I want them to be merged into one "generic" object, for this example, it would be:
{a: 1, b: {c: 'x', d:'8', k[]}}
As you can see, all nested objects are merged too. But I can't gain it. If I use Object.assign it creates new nested objects if they are different, that is duplicates them:
var res = Object.assign({}, ...list);
// res: {
a: 1,
b: {c: 'x', k: []},
b: {c: 'x', d: 8}
}
You could try the following using the reduce method:
var list = [{
a: 1,
b: {
a: 4,
k: 3
}
}, {
a: 1,
s: 11,
b: {
ab: 4,
d: 8
}
}]
var result = list.reduce(function(acc, item) {
var obj = { ...item
}
Object.keys(obj).forEach(function(item) {
if (acc[item]) { //if a property with the the key, 'item' already exists, then append to that
Object.assign(acc[item], obj[item]);
} else { // else add the key-value pair to the accumulator object.
acc[item] = obj[item];
}
})
return acc;
}, {})
console.log(result);
Deep merging is not simple to do yourself, That blog uses deep merge.
If you don't have webpack or nodejs you can use deepmerge like so:
// see https://github.com/facebook/react/blob/b5ac963fb791d1298e7f396236383bc955f916c1/src/isomorphic/classic/element/ReactElement.js#L21-L25
var canUseSymbol = typeof Symbol === 'function' && Symbol.for
var REACT_ELEMENT_TYPE = canUseSymbol ? Symbol.for('react.element') : 0xeac7
function isReactElement(value) {
return value.$$typeof === REACT_ELEMENT_TYPE
}
function isNonNullObject(value) {
return !!value && typeof value === 'object'
}
function isSpecial(value) {
var stringValue = Object.prototype.toString.call(value)
return stringValue === '[object RegExp]'
|| stringValue === '[object Date]'
|| isReactElement(value)
}
function defaultIsMergeableObject(value) {
return isNonNullObject(value)
&& !isSpecial(value)
}
function emptyTarget(val) {
return Array.isArray(val) ? [] : {}
}
function cloneUnlessOtherwiseSpecified(value, options) {
return (options.clone !== false && options.isMergeableObject(value))
? deepmerge(emptyTarget(value), value, options)
: value
}
function defaultArrayMerge(target, source, options) {
return target.concat(source).map(function(element) {
return cloneUnlessOtherwiseSpecified(element, options)
})
}
function mergeObject(target, source, options) {
var destination = {}
if (options.isMergeableObject(target)) {
Object.keys(target).forEach(function(key) {
destination[key] = cloneUnlessOtherwiseSpecified(target[key], options)
})
}
Object.keys(source).forEach(function(key) {
if (!options.isMergeableObject(source[key]) || !target[key]) {
destination[key] = cloneUnlessOtherwiseSpecified(source[key], options)
} else {
destination[key] = deepmerge(target[key], source[key], options)
}
})
return destination
}
function deepmerge(target, source, options) {
options = options || {}
options.arrayMerge = options.arrayMerge || defaultArrayMerge
options.isMergeableObject = options.isMergeableObject || defaultIsMergeableObject
var sourceIsArray = Array.isArray(source)
var targetIsArray = Array.isArray(target)
var sourceAndTargetTypesMatch = sourceIsArray === targetIsArray
if (!sourceAndTargetTypesMatch) {
return cloneUnlessOtherwiseSpecified(source, options)
} else if (sourceIsArray) {
return options.arrayMerge(target, source, options)
} else {
return mergeObject(target, source, options)
}
}
deepmerge.all = function deepmergeAll(array, options) {
if (!Array.isArray(array)) {
throw new Error('first argument should be an array')
}
return array.reduce(function(prev, next) {
return deepmerge(prev, next, options)
}, {})
}
var list = [{
a: 1,
b: {
c: 'x',
//merging 1,2 and 1,3 results in [1,2,1,3] you can change that in defaultArrayMerge
k: [1,2]
}
},
{
a: 1,
b: {
c: 'x',
k: [1,3],
d: 8
}
}];
console.log(
deepmerge.all(list)
)
You can use the reduce method. Remove the first element from the original list , that object will be the base method.
var list = [{
a: 1,
b: {
c: 'x',
k: []
}
},
{
a: 1,
b: {
c: 'x',
d: 8
}
}
];
// Remove the first element from the array. The first element will be
// the base object
// slice will return a new array without the first object
// apply reduce on this list
let _temp = list.slice(1);
let x = _temp.reduce(function(acc,curr,currIndex){
for(let keys in curr){
// checking if the base object have the same key as of current object
if(acc.hasOwnProperty(keys)){
// if base object and current object has the key then
// check if the type is an object
if(typeof curr[keys] ==='object'){
// now get the key from both the object
// & check which one is missong. Add that key and value to the
// base object
let keysFromACC = Object.keys(acc[keys]);
let keysFromCURR = Object.keys(curr[keys]);
keysFromCURR.forEach(function(item){
if(keysFromACC.indexOf(item) ===-1){
acc[keys][item] = curr[keys][item]
}
})
}
}
else{
// if the base object does not have key which current object
// has then add the key to base object
acc[keys]= curr[keys]
}
}
return acc;
},list[0]);
console.log(x)

Unflatten JS object and convert arrays

I have a function used to flatten objects like so:
let object = {
a: 1,
b: [
{ c: 2 },
{ c: 3 }
]
};
flatten(object)
// returns {
'a': 1,
'b.0.c': 2,
'b.1.c': 3
}
I need to unflatten objects, but also revert arrays to how they were. I have the following code:
unflatten(obj) {
let final = {};
for (let prop in obj) {
this.assign(final, prop.split('.'), obj[prop]);
}
return final;
}
assign(final, path, value) {
let lastKeyIndex = path.length-1;
for (var i = 0; i < lastKeyIndex; ++ i) {
let key = path[i];
if (!(key in final)) {
final[key] = {};
}
final = final[key];
}
final[path[lastKeyIndex]] = value;
}
which works for the most part, but it treats arrays like so:
{
a: 1,
b: { // Notice how b's value is now an object
"0": { c: 2 }, // Notice how these now have a key corresponding to their index
"1": { c: 3 }
}
}
Whereas I need b to be an array like before:
{
a: 1,
b: [
{ c: 2 },
{ c: 3 }
]
}
I'm at a loss for where to go from here. It needs to be able to deal with an arbitrary number of arrays like:
'a.b.0.c.0.d',
'a.b.0.c.1.d',
'a.b.1.c.0.d',
'a.b.1.c.1.d',
'a.b.1.c.2.d',
// etc
It needs to be vanilla JS, but es2015 is fine. It it assumed that any key that's a number is actually part of an array.
If anyone has any advice, it's appreciated!
When you find that key is not in final, you should check to see if the next key in the path is only digits (using a regular expression) and, if so, assign to an array instead of an object:
if (!(key in final)) {
final[key] = /^\d+$/.test(path[i + 1]) ? [] : {};
}
let object = {
a: 1,
b: [{
c: 2
},
{
c: 3
}
]
};
let flattened = {
'a': 1,
'b.0.c': 2,
'b.1.c': 3
}
function unflatten(obj) {
let final = {};
for (let prop in obj) {
assign(final, prop.split('.'), obj[prop]);
}
return final;
}
function assign (final, path, value) {
let lastKeyIndex = path.length - 1;
for (var i = 0; i < lastKeyIndex; ++i) {
let key = path[i];
if (!(key in final)) {
final[key] = /^\d+$/.test(path[i + 1]) ? [] : {};
}
final = final[key];
}
final[path[lastKeyIndex]] = value;
}
console.log(unflatten(flattened))
.as-console-wrapper { min-height: 100vh; }
You could iterate the keys and then split the string for single properties. For building a new object, you could check for number and take an array for these properties.
function setValue(object, path, value) {
var way = path.split('.'),
last = way.pop();
way.reduce(function (o, k, i, kk) {
return o[k] = o[k] || (isFinite(i + 1 in kk ? kk[i + 1] : last) ? [] : {});
}, object)[last] = value;
}
function unFlatten(object) {
var keys = Object.keys(object),
result = isFinite(keys[0][0]) ? [] : {};
keys.forEach(function (k) {
setValue(result, k, object[k]);
});
return result;
}
console.log(unFlatten({
'a': 1,
'b.0.c': 2,
'b.1.c': 3
}));
console.log(unFlatten({
'0': 1,
'1.0.c': 2,
'1.1.c': 3
}));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Compare 2 objects and remove repeating keys between

I am experimenting on objects, and what I am trying to achieve is to remove keys found in object1 if those keys exist in object2.
Here is the example:
var original = {
a: 1,
b: 2,
c: 3,
e: {
tester: 0,
combination: {
0: 1
}
},
0: {
test: "0",
2: "hello"
}
};
var badKeys = {
a: 1,
b: 2,
0: {
test: "0",
}
}
var expectedResult = {
c: 3,
e: {
tester: 0,
combination: {
0: 1
}
},
0: {
2: "hello"
}
}
I've tried using underscore difference function, but it doesn't work for objects, also not sure if this is the right function.
Can you help me to get the var expectedResult right?
You could use an iterative and recursive approach for geeting the wanted properties in a new object.
function deleteKeys(good, bad, result) {
Object.keys(good).forEach(function (key) {
if (bad[key] && typeof bad[key] === 'object') {
result[key] = {};
deleteKeys(good[key], bad[key], result[key]);
return;
}
if (!(key in bad) || good[key] !== bad[key]) {
result[key] = good[key];
}
});
}
var original = { a: 1, b: 2, c: 3, e: { tester: 0, combination: { 0: 1 } }, 0: { test: "0", 2: "hello", another: { a: { B: 2, C: { a: 3 } }, b: 2 } } },
badKeys = { a: 1, b: 2, 0: { test: "0", random: 2, another: { a: 1 } } },
result = {};
deleteKeys(original, badKeys, result);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
This would be the algorithm:
function removeDifferences (original, removeKeys) {
// Get keys of to be deleted properties.
var keys = Object.keys(removeKeys);
// Iterate all properties on removeKeys.
for (key of keys) {
// Check if property exists on original.
if (typeof original[key] !== undefined) {
// If the property is an object, call same function to remove properties.
if (typeof removeKeys[key] === 'object') {
removeDifferences(original[key], removeKeys[key]);
} else {
delete original[key];
}
}
}
return original;
}
Applied to your case:
/* Your data. */
var original = {
a: 1,
b: 2,
c: 3,
e: {
tester: 0,
combination: {
0: 1
}
},
0: {
test: "0",
2: "hello"
}
};
var badKeys = {
a: 1,
b: 2,
0: {
test: "0",
}
};
var expectedResult = {
c: 3,
e: {
tester: 0,
combination: {
0: 1
}
},
0: {
2: "hello"
}
};
/* Function */
function removeDifferences(original, removeKeys) {
// Get keys of to be deleted properties.
var keys = Object.keys(removeKeys);
// Iterate all properties on removeKeys.
for (key of keys) {
// Check if property exists on original.
if (typeof original[key] !== undefined) {
// If the property is an object, call same function to remove properties.
if (typeof removeKeys[key] === 'object') {
removeDifferences(original[key], removeKeys[key]);
} else {
delete original[key];
}
}
}
return original;
}
/* Application */
var output = removeDifferences(original, badKeys);
console.log(output);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can create recursive function that will return new object using for...in loop.
var original = {"0":{"2":"hello","test":"0"},"a":1,"b":2,"c":3,"e":{"tester":0,"combination":{"0":1}}}
var badKeys = {"0":{"test":"0"},"a":1,"b":2}
function remove(o1, o2) {
var result = {}
for (var i in o1) {
if (!o2[i]) result[i] = o1[i]
else if (o2[i]) {
if (typeof o1[i] == 'object' && typeof o2[i] == 'object') {
result[i] = Object.assign(result[i] || {}, remove(o1[i], o2[i]))
} else if (o1[i] != o2[i]) result[i] = o1[i]
}
}
return result
}
console.log(remove(original, badKeys))
Truly a job for some recursion and a bit of functional programming using a pure function. (Tested with Node v7.7.1)
"DoForAllNestedObjects" for applying some function "whattodo" on "every leaf on the dictionary tree" when there is an corresponding "leaf" in baddict.
let DoForAllNestedValues = (dict, baddict, whattodo) => {
for (let key in dict) {
if (typeof (dict[key]) === 'object' && typeof (baddict[key]) === 'object')
DoForAllNestedValues(dict[key], baddict[key], whattodo);
else
if (baddict[key])
whattodo(dict, key);
}
}
DoForAllNestedValues(original, badKeys, (obj, val) => delete obj[val]);
console.log(original);

javascript / lodash : how to do a recursive deep get in an object thanks to a sub tree object

Let's say I have a deep object like this
cont obj = {
a0: {
b0: {
c0: 0,
c1: 1
},
b1: {
c2: 2
}
}
}
and I want in one single method to retrieve a sub tree of it with this kind of api, let's name it 'retrieveDeep' :
const subObj = retrieveDeep(
obj,
{
a0: {
b0: {
c0: null
},
b1: {
c2: null
}
}
}
)
// should return
// {
// a0: {
// b0: {
// c0: 0
// },
// b1: {
// c2: 2
// }
// }
// }
I know actually that I could implement that from scratch with not so much difficulties, but if there is already a lodash (or even it looks like a bit the graphQL api shape!) function, I would happy to know it
tx
Just iterate over the properties of the pattern and make recursive calls for objects. Assign primitive values.
function retrieveDeep(object, pattern) {
function iter(o, p, r) {
Object.keys(p).forEach(function (k) {
if (k in o) {
if (typeof o[k] === 'object' && o[k] !== null) {
r[k] = {};
iter(o[k], p[k], r[k]);
return;
}
r[k] = o[k];
}
});
}
var result = {};
iter(object, pattern, result);
return result;
}
var obj = { a0: { b0: { c0: 0, c1: 1 }, b1: { c2: 2 } } },
subObj = retrieveDeep(obj, { a0: { b0: { c0: null }, b1: { c2: null } } });
console.log(subObj);
With lodash 1+, there is a shortest way using _.at(object, [paths]) :
let exist = {
a:{
b: {c: 3}
},
ZZ: 2
};
let resExist = _.at(exist, 'a.b.c').pop() // => 3
let notExit = { a : 1 }
let resNoExist = _.at(notExit, 'a.b.c').pop() // undefined
console.log('resExist', resExist)
console.log('resNoExist', resNoExist)
<script src="https://cdn.jsdelivr.net/lodash/4.17.4/lodash.min.js"></script>
see documention
Lodash get method: _.get(object, path, [defaultValue])
var object = {
'a': [
{
'b': {
'c': 3
}
}
]
};
_.get(object, 'a[0].b.c');
// => 3
_.get(object, ['a', '0', 'b', 'c']);
// => 3
_.get(object, 'a.b.c', 'default');
// => 'default

Categories