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);
Related
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)
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);
This question already has answers here:
How can I access and process nested objects, arrays, or JSON?
(31 answers)
Closed 5 years ago.
I need to do a deep-iteration of a javascript object that can have nested objects and arrays and I need to execute a function on all of the numeric values and modify the object.
For example, lets say I need to multiply every number by 2.
const foo = (obj) => {
// multiply every numeric value by 2
};
const modified = foo({
a: 0,
b: 3,
c: {
d: 4,
e: {
f: 6,
g: [ 0, 3, 7, 3 ]
}
}
});
The value of modified should be:
{
a: 0,
b: 6,
c: {
d: 8,
e: {
f: 12,
g: [ 0, 6, 14, 6 ]
}
}
}
Since people typically want to know what you've tried, here's how far I got before being completely stumped.
const obj = {};
for(key in object) {
const item = object[key];
if(typeof item === 'object') {
// The levels deep is dynamic, so how would I keep doing this..
} else if(typeof item === 'array') {
obj[key] = item.map((a, b) => a * 2);
} else if(!isNaN(item)) {
obj[key] = item * 2;
}
}
Using recursion and expanding on your solution
function multiplyByTwo(objectToParse) {
const obj = {};
for (key in objectToParse) {
const item = object[key];
if (typeof item === 'object') {
obj[key] = multiplyByTwo(item);
} else if (typeof item === 'array') {
obj[key] = item.map((a, b) => a * 2);
} else if (!isNaN(item)) {
obj[key] = item * 2;
}
}
return obj;
}
const result = multiplyByTwo(object);
You probably want recursion in this case. This implementation works for any type of object and mapping function you give it, aka extremely generic
function mapper(obj, mappingFn, result) {
if (!result)
result = {};
Object.keys(obj)
.forEach(key => {
switch (typeof obj[key]) {
case 'string':
case 'number':
result[key] = mappingFn(obj[key]);
break;
// if obj[key] is an array, it still returns 'object', so we are good
case 'object':
mapper(obj[key], mappingFn, result);
break;
}
});
return result;
}
let data = {
a: 0,
b: 3,
c: {
d: 4,
e: {
f: 6,
g: [ 0, 3, 7, 3 ]
}
}
};
let result = mapper(data, value => value * 2);
console.log(result) // everything should be multiplied by 2
There's my solution.
const foo = (obj, operation) => {
let afterObj = obj;
for(item in afterObj) {
if(typeof afterObj[item] == "object") {
foo(afterObj[item], operation);
} else if (typeof afterObj[item] = "string") {
} else {
afterObj[item] = operation(afterObj[item]);
}
}
return afterObj;
};
const modified = foo({
a: 0,
b: 3,
c: {
d: 4,
e: {
f: 6,
g: [ 0, 3, 7, 3 ]
}
}
}, function(x) { return x*2 });
I am trying to remove empty objects inside an object, here is an example with the expected output:
var object = {
a: {
b: 1,
c: {
a: 1,
d: {},
e: {
f: {}
}
}
},
b: {}
}
var expectedResult = {
a: {
b: 1,
c: {
a: 1,
}
}
}
I tried using some examples from other StackOverflow questions, however those are just for one level objects.
Basic function that removes empty objects
First start with a function that only works with a single level of nesting.
This function removes all properties that reference an empty object:
function clearEmpties(o) {
for (var k in o) {
if (!o[k] || typeof o[k] !== "object") {
continue // If null or not an object, skip to the next iteration
}
// The property is an object
if (Object.keys(o[k]).length === 0) {
delete o[k]; // The object had no properties, so delete that property
}
return o;
}
}
Handling nested objects using recursion
Now you want to make it recursive so that it will operate on nested objects. So we already have tested if o[k] is an object, and we've tested if there are properties, so if there are, we simply call the function again with that nested object.
function clearEmpties(o) {
for (var k in o) {
if (!o[k] || typeof o[k] !== "object") {
continue // If null or not an object, skip to the next iteration
}
// The property is an object
clearEmpties(o[k]); // <-- Make a recursive call on the nested object
if (Object.keys(o[k]).length === 0) {
delete o[k]; // The object had no properties, so delete that property
}
}
return o;
}
So just as the original call to clearEmpties removes properties of the given object that reference an empty object, likewise the recursive call will do the same for the nested objects.
Live demo:
var object = {
a: {
b: 1,
c: {
a: 1,
d: {},
e: { // will need to be removed after f has been removed
f: {}
}
}
},
b: {}
};
clearEmpties(object);
console.log(object);
function clearEmpties(o) {
for (var k in o) {
if (!o[k] || typeof o[k] !== "object") {
continue
}
clearEmpties(o[k]);
if (Object.keys(o[k]).length === 0) {
delete o[k];
}
}
return o;
}
Short version using Underscore and functional style
function clearEmpties(o) {
if (_.isFunction(o) || !_.isObject(o)) return o;
return _.chain(o)
.mapObject(clearEmpties)
.pick(p => !(_.isObject(p) && _.isEmpty(p)))
.value();
}
Short version using lodash and functional style - works with treeshaking
import { isFunction, isObject, isEmpty, isArray, isPlainObject, fromPairs } from "lodash-es";
const removeEmtpyObjects = (o) => {
if (isFunction(o) || !isPlainObject(o)) return o;
if (isArray(o)) return o.map(removeEmtpyObjects);
return fromPairs(
Object.entries(o)
.map(([k, v]) => [k, removeEmtpyObjects(v)])
.filter(([k, v]) => !(v == null || (isObject(v) && isEmpty(v))))
);
};
I had this same problem and with the addition that my object may contain arrays with empty elements that need to be removed as well.
I ended up with this quick and dirty solution.
If you want to define what "empty" means to you, I also added a different function. In my case, I also needed empty strings.
function isEmpty(obj) {
if (obj === '' || obj === null || JSON.stringify(obj) === '{}' || JSON.stringify(obj) === '[]' || (obj) === undefined || (obj) === {}) {
return true
} else {
return false
}
}
function removeEmpty(o) {
if (typeof o !== "object") {
return o;
}
let oKeys = Object.keys(o)
for (let j = 0; j < oKeys.length; j++) {
let p = oKeys[j]
switch (typeof (o[p])) {
case 'object':
if (Array.isArray(o[p])) {
for (let i = 0; i < o[p].length; i++) {
o[p][i] = removeEmpty(o[p][i])
if (isEmpty(o[p][i])) {
o[p].splice(i, 1)
i--
}
}
if (o[p].length === 0) {
if (Array.isArray(o)) {
o.splice(parseInt(p), 1)
j--
} else {
delete o[p]
}
}
} else {
if (isEmpty(o[p])) {
delete o[p]
} else {
o[p] = removeEmpty(o[p])
if (isEmpty(o[p])) {
delete o[p]
}
}
}
break
default:
if (isEmpty(o[p])) {
delete o[p]
}
break
}
}
if (Object.keys(o).length === 0) {
return
}
return o
}
Input:
var a = {
b: 1,
c: {
d: [1, [[], [], [[[1], []]]], [2, [[], [[]]]], [], [[]]]
},
e: {
f: [{}, { g: 1 }]
},
h: {
i: { j: { k: undefined, l: null, m: { n: "", o: 1 } } }
},
p: { q: { r: 1 } }
}
removeEmpty(a)
Output:
{
"b": 1,
"c": {
"d": [1, [[[[1]]]], [2]]
},
"e": {
"f": [{"g": 1}]
},
"h": {
"i": {
"j": {
"m": {
"o": 1
}
}
}
},
"p": {
"q": {
"r": 1
}
}
}
function clean(obj) {
for (var propName in obj) {
if (obj[propName] === null || obj[propName] === undefined) {
delete obj[propName]; }
}
}
EDIT:
function clean(obj) {
for (var propName in obj) {
if(typeof obj[propName]=="object")
clean(obj[propName])
if (obj[propName] === null || obj[propName] === undefined)
delete obj[propName];
}
}
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; }