I want to be able to merge two objects by adding their values together.
> a
{ "a" : 1, "b" : 3, "d": {"da": 1}}
> b
{ "a" : 1, "c" : 34, "d": {"da": 2} }
I want to obtain :
> { "a" : 2, "b": 3, "c" : 34, "d": {"da": 3} }
I've tried this but it doesn't work :
function MergeRecursive(obj1, obj2) {
for (var p in obj2) {
try {
// Property in destination object set; update its value.
if ( obj2[p].constructor==Object ) {
obj1[p] += MergeRecursive(obj1[p], obj2[p]);
} else {
obj1[p] = obj2[p];
}
} catch(e) {
// Property in destination object not set; create it and set its value.
obj1[p] = obj2[p];
}
}
return obj1;
}
Any ideas ?
First, let's define an abstract function that applies a func to a combination of two objects, and then use it together with the summation function.
function merge(x, y, fn) {
var result = {};
Object.keys(x).forEach(function(k) {
result[k] = x[k];
});
Object.keys(y).forEach(function(k) {
result[k] = k in x ? fn(x[k], y[k]) : y[k];
});
return result;
}
function add(p, q) {
if(typeof p === 'object' && typeof q === 'object') {
return merge(p, q, add);
}
return p + q;
}
a = { "a" : 1, "b" : 3, "d": {"da": 1}};
b = { "a" : 1, "c" : 34, "d": {"da": 2}};
sum = merge(a, b, add)
document.write('<pre>'+JSON.stringify(sum,0,3));
merge can be also written in a more functional style, like this:
function clone(x) {
return Object.keys(x).reduce(function(res, k) {
res[k] = x[k];
return res;
}, {});
}
function merge(x, y, fn) {
return Object.keys(y).reduce(function(res, k) {
res[k] = k in x ? fn(x[k], y[k]) : y[k];
return res;
}, clone(x));
}
If you're fine with the first object being changed, you can skip the clone step and just pass x.
My attempt
function MergeRecursive(obj1, obj2) {
var k = Object.keys(obj1), i = 0;
for (var p in obj2) {
if(typeof obj1[p] == 'object' && typeof obj2[p] == 'object')
obj2[p] = MergeRecursive(obj1[p],obj2[p]);
else if(obj1[p] != null)
obj2[p] += obj1[p];
else
obj2[k[i]] = obj1[k[i]];
i++;
}
return obj2;
}
To use as
MergeRecursive({ "a" : 1, "b" : 3, "d": {"da": 1}},{ "a" : 1, "c" : 34, "d": {"da": 2} })
Create a resultArray to avoid complexity with obj1. Then just add each value in obj1 to the resultArray with the value of obj2 accumulated if the corresponding key in obj2 exists.
Then add each key in obj2 that was not yet added by the first iteration to the resultArray, so that you don't lose the data from obj2.
function MergeRecursive(obj1, obj2) {
resultArray = [];
for (var key in obj1) {
if ( typeof obj1[key] === 'object') {
resultArray[key] = MergeRecursive(obj1[p], obj2[p]);
} else {
resultArray[key] = obj1[key] + obj2[p] ?? 0;
}
}
for (var key in obj2) {
if (key in resultArray){
continue;
}
resultArray[key] = obj2[key];
}
return resultArray;
}
Related
I have a deeply nested object:
{ a: { b: { c: 3 }, d: 4 } }.
How to get all namespaces within this object?
So, I need to get:
['a.b.c', 'a.d'].
You can create recursive function using for...in loop.
var obj = {a: {b: {c: 3} }, d: 4 }
function getKeys(data, prev) {
var result = []
for (var i in data) {
var dot = prev.length ? '.' : '';
if (typeof data[i] == 'object') result.push(...getKeys(data[i], prev + dot + i))
else result.push(prev + dot + i)
}
return result;
}
console.log(getKeys(obj, ''))
Instead of for...in loop you can use Object.keys() and reduce().
var obj = {a: {b: {c: 3} }, d: 4 }
function getKeys(data, prev) {
return Object.keys(data).reduce(function(r, e) {
var dot = prev.length ? '.' : '';
if (typeof data[e] == 'object') r.push(...getKeys(data[e], prev + dot + e))
else r.push(prev + dot + e)
return r;
}, [])
}
console.log(getKeys(obj, ''))
var t = {a: {b: {c: 3} }, d: 4 };
path (t, '');
function path(t, sofar) {
if (Object.keys(t).length === 0)
console.log(sofar.substr(1));
var keys = Object.keys(t);
for (var i = 0 ; i < keys.length ; ++i) {
path(t[keys[i]], sofar+'.'+keys[i]);
}
}
You could create a script in order to flatten the object and return the keys. You could also think to convert it to an array and use the default flatten of arrays. Here an example of flattening the object.
var flattenObject = function(ob) {
var toReturn = {};
for (var i in ob) {
if (!ob.hasOwnProperty(i)) continue;
if ((typeof ob[i]) == 'object') {
var flatObject = flattenObject(ob[i]);
for (var x in flatObject) {
if (!flatObject.hasOwnProperty(x)) continue;
toReturn[i + '.' + x] = flatObject[x];
}
} else {
toReturn[i] = ob[i];
}
}
return toReturn;
};
var obj = {a: {b: {c: 3} }, d: 4 }
console.log(Object.keys(flattenObject(obj))); // ['a.b.c', 'd']
p.s. your object in the question has a mistake, or what you want is not what you are asking. d is at the same level of a, so you can't achieve "a.d", but "d"
You could check the keys and iterate otherwise push the path to the result set.
function getKeys(object) {
function iter(o, p) {
var keys = Object.keys(o);
keys.length ?
keys.forEach(function (k) { iter(o[k], p.concat(k)); }):
result.push(p.join('.'));
}
var result = [];
iter(object, []);
return result;
}
var object = { a: { b: { c: 3 } }, d: 4 };
console.log(getKeys(object));
.as-console-wrapper { max-height: 100% !important; top: 0; }
How to turn a JSON To a properties array in JS, no matter what's the JSON's depth ?
JSON
{
"foo": {
"bar": {
"baz": "UP"
}
}
}
key/value properties
{
"foo.bar.baz": "UP"
}
One-level solution
My current code only treats one level, instead of n:
var user = {name: 'Corbin', age: 20, location: 'USA'},
key;
for (key in user) {
if (user.hasOwnProperty(key)) {
console.log(key + " = " + user[key]);
}
}
Thank you :D
Basically, if a member is an object, make a recursive call, otherwise, update the output object:
data = {
foo: "hello",
bar: {
baz: {
qux: "UP"
},
spam: 'ham'
}
}
function unwrap(obj, prefix) {
var res = {};
for (var k of Object.keys(obj)) {
var val = obj[k],
key = prefix ? prefix + '.' + k : k;
if (typeof val === 'object')
Object.assign(res, unwrap(val, key)); // <-- recursion
else
res[key] = val;
}
return res;
}
console.log(unwrap(data))
I want to implement a function that returns an array of property values if the value is primitive (non-object or array) and property name starts with prefix.
For example
var values = function (obj, prefix) { ... }
var testObj = {
'a': 1,
'ab': [
{
'c': 2,
'ac': true
}
]
};
As a result of values(testObj, 'a') function invocation I expect to get such array of primitives: [1, true].
Here is my try:
var values = function (obj, prefix) {
var res = [];
for (var i in obj) {
if (i.startsWith(prefix)) {
var v = obj[i];
if (typeof v === 'object') {
var r0 = arguments.callee(v, prefix);
res.push(r0);
} else {
res.push(v);
}
}
}
return res;
};
But it returns a wrong result: [1, []]. How can I fix it?
You could use a recursive approach for the values, you need.
function values(obj, prefix) {
var result = [];
Object.keys(obj).forEach(function (k) {
if (obj[k] !== null && typeof obj[k] === 'object') {
result = result.concat(values(obj[k], prefix));
} else if (k.startsWith(prefix)) {
result.push(obj[k]);
}
});
return result;
}
var testObj = { 'a': 1, 'ab': [{ 'c': 2, 'ac': true }] },
result = values(testObj, 'a');
console.log(result);
Following code works.
var testObj = {
'a': 1,
'ab': [
{
'c': 2,
'ac': true
}
]
};
var values = function (obj, prefix) {
var res = [];
if(Array.isArray(obj)){
for(var j in obj){
res = res.concat(arguments.callee(obj[j], prefix));
}
}
else if(typeof obj == "object") {
for (var i in obj) {
if (i.startsWith(prefix)) {
var v = obj[i];
if (typeof v === 'object') {
res = res.concat(arguments.callee(v, prefix));
} else {
res.push(v);
}
}
}
}
return res;
};
console.log(values(testObj, 'a'));
This might be what you are looking for;
var testObj = {
'a': 1,
'ab': [
{
'c': 2,
'ac': true
}
]
};
getValues = (o,x) => Object.keys(o)
.reduce((p,k) => p.concat(typeof o[k] === "object" ? getValues(o[k],x)
: k.indexOf(x) >= 0 ? o[k]
: [])
,[]);
console.log(getValues(testObj,"a"));
(My city energy is weak.)
Where the main problem is in the i.startsWith(prefix) condition. It avoids you to enter a object without property name including #prefix inside a array. For example:
{ a: 1, ab: [ /* 0: { 'c': 2, 'ac': true } */ ] }
As you see, the object in the array is ignored since its property name is 0, that's its index.
If you really want to get this result: [1, true] you'll have to skip the array and return the first item to res.push.
var values = function (obj, prefix) {
var res = [];
for (var i in obj) {
if (i.startsWith(prefix)) {
var v = obj[i];
if (typeof v === 'object') {
var isArray = v instanceof Array;
var r0 = arguments.callee(isArray ? v[0] : v, prefix);
res.push(isArray && r0.length === 1 ? r0[0] : r);
} else {
res.push(v);
}
}
}
return res;
};
var testObj = {
'a': 1,
'ab': [
{
'c': 2,
'ac': true
}
]
};
var res = [];
var values = function (obj, prefix) {
for (var i in obj) {
var v = obj[i];
//Prefix check line can be moved here if you want to check the prefix for object
if (typeof v === 'object') {
arguments.callee(v, prefix);
} else {
if (i.startsWith(prefix)) { //Prefix Check
res.push(v);
}
}
}
return res;
};
console.log(values(testObj,'a'));
Please check this, this gives the output that you wanted.
I have an nested object that I want to update it with values provided by object that contains similar structure but only the properties that I want updated. Creating a new result instead of modifying the initial objects is great too.
var initial =
{
a: 1,
b : {
c : 2,
d : 3
},
f: 5
};
var update = {
a: 2,
b: {
d: 2
}
};
function updateFunction (a,b) { return a+b;};
var result=
{
a: 3, // updateFunction (1,2)=> 3
b : {
c : 2,
d :5 // updateFunction (3,2) => 5
},
f: 5
};
Have not tested fully, but maybe,
assuming objects are simple as stated,
function updateFunction (a,b) { return a + b;};
function recurse(initial, update){
for(prop in initial){
if({}.hasOwnProperty.call(initial, prop) && {}.hasOwnProperty.call(update, prop)){
if(typeof initial[prop] === 'object' && typeof update[prop] === 'object'){
recurse(initial[prop], update[prop]);
}
else{
initial[prop] = updateFunction(initial[prop], update[prop]);
}
}
}
}
recurse(initial, update);
EDIT
If result is expected without changing initial
function updateFunction (a,b) { return a + b;};
function recurse(initial, update){
var result = {};
for(prop in initial){
if({}.hasOwnProperty.call(initial, prop)){
result[prop] = initial[prop];
if({}.hasOwnProperty.call(update, prop)){
if(typeof initial[prop] === 'object' && typeof update[prop] === 'object'){
result[prop] = recurse(initial[prop], update[prop]);
}
else{
result[prop] = updateFunction(initial[prop], update[prop]);
}
}
}
}
return result;
}
var result = recurse(initial, update);
hope this helps.
Here's how I'd do it:
// The parallel to Array.map
Object.map = function(obj, f) {
var result = {};
for(k in obj)
if({}.hasOwnProperty.call(obj, k))
result[k] = f(k, obj[k]);
return result;
}
// Takes two objects and uses `resolve` to merge them
function merge(a, b, resolve) {
return Object.map(a, function(k, a_value) {
if(k in b)
return resolve(a_value, b[k]);
else
return a_value;
});
}
// same as above, but recursing when an object is found
function recursive_merge(a, b, resolve) {
return merge(a, b, function(a_value, b_value) {
if(typeof a_value == 'object' && typeof b_value == 'object')
return recursive_merge(a_value, b_value, resolve);
else
return resolve(a_value, b_value);
});
}
result = recursive_merge(initial, update, function(a, b) { return a + b; })
What I am trying to do is create an object which has x number of properties in it along with y number of properties from another object I retrieve from calling another method. Is this possible to do?
ie Here is my pseudo code. Basically I want mydata to include all the properties from the object I get back from getMoreData()
Maybe this is not possible but I have no idea how to do it if it is :(
var mydata =
{
name: "Joe Soap",
dob: "01-01-2001",
otherData: {
hasCat: true,
hasDog : false
}
var otherData = getMoreData();
for(var prop in otherData)
{
create additional property in mydata for prop;
}
}
function getMoreData()
{
var moreData =
{
middleName: "tom",
location: "uk"
}
return otherData;
}
You can't declare variables or use other statements like for in the middle of an object literal. You need to define the basic object first, and then add properties afterwards:
var mydata = {
name: "Joe Soap",
dob: "01-01-2001",
otherData: {
hasCat: true,
hasDog : false
}
};
var otherData = getMoreData();
for(var prop in otherData) {
mydata[prop] = otherData[prop];
}
Also your getMoreData() function needs to return the moreData variable, not otherData:
function getMoreData() {
var moreData = {
middleName: "tom",
location: "uk"
}
return moreData;
}
Demo: http://jsfiddle.net/nnnnnn/TQf5H/
In Addition to nnnnnn's Answer.
Be careful that the above does only a shallow copy.
Meaning if you have an Object it gets copied by reference.
Here is a bit more advanced merge function. i'll explain it a bit more in detail later.
Merge Function
var merge = (function () {
var initThis = this;
return function () {
var len = arguments.length - 1,
srt, tmp;
if ("function" === typeof arguments[arguments.length - 1]) srt = arguments[arguments.length - 1];
else {
srt = function (a, b, prop) {
if (null === prop) return a;
return a[prop];
};
len++;
}
var merge = this === initThis ? {} : this;
for (var i = 0; i < len; i++) inner(arguments[i], merge);
function inner(obj2, obj1) {
var type = ({}).toString.call(obj2);
if (type == "[object Object]") {
if (!obj1) obj1 = {};
if (typeof obj1 != "object") obj1 = (tmp = srt(obj1, obj2, null), tmp) === obj2 ? {} : tmp; //If obj2 is returned, set to empty obj to allow deep cloning
for (var prop in obj2) {
var isObj = "object" === typeof obj2[prop];
if (!obj1[prop] && isObj) obj1[prop] = inner(obj2[prop]);
else if (obj1[prop] && isObj) obj1[prop] = inner(obj2[prop], obj1[prop]);
else if (obj1[prop]) obj1[prop] = srt(obj1, obj2, prop) || obj1[prop];
else obj1[prop] = obj2[prop];
}
} else if (type == "[object Array]") {
if (!obj1) obj1 = [];
if (typeof obj1 != "object") obj1 = (tmp = srt(obj1, obj2, null), tmp) === obj2 ? [] : tmp
for (var i = 0; i < obj2.length; i++) if (!obj1[i] && typeof obj2[i] == "object") obj1[i] = inner(obj2[i]);
else if (obj1[i] && typeof obj2[i] == "object") obj1[i] = inner(obj2[i], obj1[i]);
else if (obj1[i]) obj1[i] = (function (i) {
return srt(obj1, obj2, i)
})(i) || obj1[i];
else obj1[i] = obj2[i];
}
return obj1;
}
return merge;
};
})();
Sample Data
var target = {
unique: "a",
conflict: "target",
object: {
origin: "target"
},
typeConflict: "primitive",
arr: [1, 5, 3, 6]
};
var mergeFrom = {
other: "unique",
conflict: "mergeFrom",
object: {
origin: "mergeFrom",
another: "property"
},
typeConflict: ["object"],
arr: [3, 2, 7, 1]
};
Usage
The merge function accepts n parameters.
And merges all passed Objects into one and returns it.
You can set a context with .call into which the Objects get merged.
A conflict function can be passed as last argument.
Which gets called if an propertie already exists.
It gets called with 3 parameters.
a,b , prop where
a is the first object
b is the second object
prop is the property which gets merged currently.
If theres a type conflict. e.g value 1 is a primitive and value 2 an Object
prop is null
Example Calls
Calling the merge function with a context, other than the scope its in, it merges the Object into it.
merge.call(target,mergeFrom)
Calling it, passing a conflict function that always uses object b properties.
var result = merge(target,mergeFrom,function (a,b,prop) {
if (prop === null) return b
return b[prop]
})
Calling it passing 3 Arrays and a conflict function that pushes the values into the first
var mergedArr = merge({arr:[1]},{arr:[2]},{arr:[3]},function (a,b,prop) {
if (a[prop] != b[prop]) a.push(b[prop])
return a[prop]
})
Heres an example on JSBin
Outputs
Example 1 - console.log(target)
{
"arr": [1, 5, 3, 6],
"as": "arguments",
"conflict": "target",
"more": "Objects",
"object": {
"another": "property",
"origin": "target"
},
"other": "unique",
"typeConflict": "primitive",
"unique": "a"
}
Example 2 - console.log(result)
{
"arr": [3, 2, 7, 1],
"conflict": "mergeFrom",
"object": {
"another": "property",
"origin": "mergeFrom"
},
"other": "unique",
"typeConflict": ["object"],
"unique": "a"
}
Example 3 - console.log(mergedArr)
{
"arr": [1, 2, 3]
}