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.
Related
I'm trying to use a recursive function to get the last key value form a simple json using javascript
I have this json:
{
'a': {
'b': {
'c': 12,
'd': 'Hello World'
},
'e': [1,2,3]
}
}
And my expected result is:
{
'a/b/c': 12,
'a/b/d': 'Hello World',
'a/e': [1,2,3]
}
I'm trying with:
function getDeepKeys(obj) {
var keys = [];
for (var key in obj) {
keys.push(key);
if (typeof obj[key] === "object") {
var subkeys = getDeepKeys(obj[key]);
keys = keys.concat(subkeys.map(function (subkey) {
return key + "/" + subkey;
}));
}
}
return keys;
}
But for some reason, it returns me:
a/b/c/d/e/0/1/, I'm not sure why it is adding those numbers there.
Someone has an idea about how I can do it?
You can do it iteratively with an explicit stack which has less overhead than recursion and won't blow the call stack:
const pathify = o => {
const paths = {};
const stack = [[o, []]];
while (stack.length) {
const [curr, path] = stack.pop();
for (const k in curr) {
if (typeof curr[k] === "object" && !Array.isArray(curr[k])) {
stack.push([curr[k], path.concat(k)]);
}
else {
paths[`${path.join("/")}/${k}`] = curr[k];
}
}
}
return paths;
};
console.log(pathify({'a':{'b':{'c':12,'d':'Hello World'},'e':[1,2,3]}}));
I think you may be making it more complicated than necessary. You can test for an array with Array.isArray and an non-object (typeof !== 'object) and just return the path and value. Otherwise recurse for each entry. reduce() is good for that. Passing the current path as an argument to the recursive function is convenient too:
let obj = {'a': {'b': {'c': 12,'d': 'Hello World'},'e': [1,2,3]}}
function getValues(obj, path = []){
return (Array.isArray(obj) || typeof obj !== 'object')
? {[path.join('/')]: obj}
: Object.entries(obj).reduce((acc, [key, val]) =>
Object.assign(acc, getValues(val, path.concat(key)) )
, {})
}
console.log(getValues(obj))
You can use the function Object.keys to loop the keys of the objects and with recursion, you can go deeper through the nested objects.
let obj = {'a': {'b': {'c': 12,'d': 'Hello World'},'e': [1,2,3]}},
result = Object.create(null),
loop = function (o, arr, result) {
Object.keys(o).forEach(k => {
arr.push(k);
if (typeof o[k] === 'object' && !Array.isArray(o[k])) loop(o[k], arr, result);
else result[arr.join('/')] = o[k];
arr.splice(-1);
});
};
loop(obj, [], result);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
doc = {
'a': {
'b': {
'c': 'hello'
},
'd': {
'c': 'sup',
'e': {
'f': 'blah blah blah'
}
}
}
}
function get(json, path) {
var str = path.split('.');
var temp = json;
var arr = [];
var keystr = "";
for (var i = 0; i < str.length; i++) {
if (str[i] != "*") {
keystr += str[i] + ".";
if (temp[str[i]] === undefined)
break;
else {
temp = temp[str[i]];
if (i == str.length - 1) {
var nObj = {};
nObjKey = keystr.substr(0, keystr.length - 1);
nObj[nObjKey] = temp
// console.log("Obj check" + JSON.stringify(nObj) + keystr)
arr.push(nObj);
}
}
} else {
for (var key in temp) {
var concat = key + "."
for (var j = i + 1; j < str.length; j++)
concat += str[j] + ".";
if (temp[key] !== undefined && temp[key] instanceof Object) {
var m = keystr + concat.substr(0, concat.length - 1);
var obj = (get(temp, concat.substr(0, concat.length - 1)));
if (obj != "") {
// console.log("existing arr "+JSON.stringify(arr))
obj[m] = (obj[0])[concat.substr(0, concat.length - 1)]
// console.log("hello "+JSON.stringify(obj) + " end hello")
arr.push(obj);
}
} else if (temp[key] !== undefined && i == str.length - 1) {
// arr.push(temp);
}
}
}
}
return arr;
}
var result = (get(doc, 'a.*.e'))
console.log(result)
For input of 'a.*.e' the output should be {'a.d.e': {'f': 'blah blah blah'}}}. But I get all the replacement for wild card as well in the array. I am sure something is wrong but not able to detect it. Help would be appreciated.
You could change the structure of the operation a little bit with a recursive approach and an exit early exit often paradigm with checking of single parts with exit options, like
length, a part result is found,
falsy or not object types,
part at index is a star, then iterate all keys from the object, or
the part at index is a key, then call the function again.
At the end, with a found path, joint the path and generate a new property with the actual value of the object.
function get(object, path) {
function iter(o, p, i) {
if (i === parts.length) {
result[p.join('.')] = o;
return;
}
if (!o || typeof o !== 'object') {
return;
}
if (parts[i] === '*') {
Object.keys(o).forEach(function (k) {
iter(o[k], p.concat(k), i + 1);
});
return;
}
if (parts[i] in o) {
iter(o[parts[i]], p.concat(parts[i]), i + 1);
}
}
var result = {},
parts = path.split('.');
iter(object, [], 0);
return result;
}
var doc = { a: { b: { c: 'hello' }, d: { c: 'sup', e: { f: 'blah blah blah' } } } };
console.log(get(doc, 'a.*.e'));
console.log(get(doc, 'a.*.c'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Version with * as wildcard for any level.
function get(object, path) {
function iter(o, p, i) {
if (i === parts.length) {
result[p.join('.')] = o;
return;
}
if (!o || typeof o !== 'object') {
return;
}
if (parts[i] === '*') {
Object.keys(o).forEach(function (k) {
iter(o[k], p.concat(k), i);
iter(o[k], p.concat(k), i + 1);
});
return;
}
if (parts[i] in o) {
iter(o[parts[i]], p.concat(parts[i]), i + 1);
}
}
var result = {},
parts = path.split('.');
iter(object, [], 0);
return result;
}
var doc = { a: { b: { c: 'hello' }, d: { c: 'sup', e: { f: 'blah blah blah' } } } };
console.log(get(doc, 'a.*.e'));
console.log(get(doc, 'a.*.c'));
console.log(get(doc, 'a.*.f'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
First of all, since your desired output {'a.d.e': {'f': 'blah blah blah'}}} does not contain any array, but only plain objects, you should not need the variable arr in your code.
Instead, return nObj as function result, and declare it at the start, never clearing it.
Secondly, when you come back from the recursive call, the results need to be copied while prefixing the paths with what you already had. Note that checking for an empty array should not be done with != "", but anyway, you don't need that any more.
You could write this from scratch in different ways (see solution at end of answer), but I have first adapted your code to only change the bare minimum, with comments where I made the changes to make it work:
function get(json, path) {
var str = path.split('.');
var temp = json;
var arr = [];
var keystr = "";
// *** Define here the object to return
var nObj = {};
for (var i = 0; i < str.length; i++) {
if (str[i] != "*") {
keystr += str[i] + ".";
if (temp[str[i]] === undefined)
break;
else {
temp = temp[str[i]];
if (i == str.length - 1) {
// *** Move this to start of the function
//var nObj = {};
nObjKey = keystr.substr(0, keystr.length - 1);
nObj[nObjKey] = temp
}
}
} else {
for (var key in temp) {
var concat = key + "."
for (var j = i + 1; j < str.length; j++)
concat += str[j] + ".";
if (temp[key] !== undefined && temp[key] instanceof Object) {
var m = keystr + concat.substr(0, concat.length - 1);
var obj = get(temp, concat.substr(0, concat.length - 1));
// *** Return value is object with path(s) as keys
// *** Don't compare array with string
//if (arr != "") {
// *** Iterate over the returned object properties, and prefix them
for (var deepKey in obj) {
nObj[keystr + deepKey] = obj[deepKey];
}
//*** No need for array; we already have the object properties
//arr.push(obj);
//}
// *** No need for array
//} else if (temp[key] !== undefined && i == str.length - 1) {
// arr.push(temp);
}
}
}
}
// *** Return object
return nObj;
}
var doc = {
'a': {
'b': {
'c': 'hello'
},
'd': {
'c': 'sup',
'e': {
'f': 'blah blah blah'
},
},
'g': {
'e': {
'also': 1
}
}
}
}
var result = (get(doc, 'a.*.e'));
console.log(result);
Please also consider not name objects json when they are not: JSON is a text format, JavaScript object variables are not the same thing as JSON.
Compact ES6 solution
When you are used to array functions like reduce and a functional programming style, the following compact ES6 solution might appeal to you:
function get(obj, path) {
if (typeof path === 'string') path = path.split('.');
return !path.length ? { '': obj } // Match
: obj !== Object(obj) ? {} // No match
: (path[0] === '*' ? Object.keys(obj) : [path[0]]) // Candidates
.reduce( (acc, key) => {
const match = get(obj[key], path.slice(1)); // Recurse
return Object.assign(acc, ...Object.keys(match).map( dotKey =>
({ [key + (dotKey ? '.' + dotKey : '')]: match[dotKey] })
));
}, {});
}
const doc = {
'a': {
'b': {
'c': 'hello'
},
'd': {
'c': 'sup',
'e': {
'f': 'blah blah blah'
},
},
'g': {
'e': {
'also': 1
}
}
}
};
const result = get(doc, 'a.*.e');
console.log(result);
List monad
Here's a solution which borrows ideas from the List monad to represent a computation which may have 0, 1, or more results. I'm not going to cover it in detail and I've only included enough of the List type to get a working solution. If you're interested in this sort of approach, you can do some more research on the topic or ask me a follow-up question.
I'm also using an auxiliary find function which is the recursive helper for get which operates the array of keys that get prepares
If you like this solution, I've written about the list monad in some other answers; you might find them helpful ^_^
const List = xs =>
({
value:
xs,
bind: f =>
List (xs.reduce ((acc, x) =>
acc.concat (f (x) .value), []))
})
const find = (path, [key, ...keys], data) =>
{
if (key === undefined)
return List([{ [path.join('.')]: data }])
else if (key === '*')
return List (Object.keys (data)) .bind (k =>
find ([...path, k], keys, data[k]))
else if (data[key] === undefined)
return List ([])
else
return find ([...path, key], keys, data[key])
}
const get = (path, doc) =>
find ([], path.split ('.'), doc) .value
const doc =
{a: {b: {c: 'hello'},d: {c: 'sup',e: {f: 'blah blah blah'}}}}
console.log (get ('a.b.c', doc))
// [ { 'a.b.c': 'hello' } ]
console.log (get ('a.*.c', doc))
// [ { 'a.b.c': 'hello' }, { 'a.d.c': 'sup' } ]
console.log (get ('a.*', doc))
// [ { 'a.b': { c: 'hello' } },
// { 'a.d': { c: 'sup', e: { f: 'blah blah blah' } } } ]
console.log (get ('*.b', doc))
// [ { 'a.b': { c: 'hello' } } ]
Native arrays only
We don't have to do fancy List abstraction in order to achieve the same results. In this version of the code, I'll show you how to do it using nothing but native Arrays. The only disadvantage of this code is the '*'-key branch gets a little complicated by embedding the flatmap code inline with our function
const find = (path, [key, ...keys], data) =>
{
if (key === undefined)
return [{ [path.join ('.')]: data }]
else if (key === '*')
return Object.keys (data) .reduce ((acc, k) =>
acc.concat (find ([...path, k], keys, data[k])), [])
else if (data[key] === undefined)
return []
else
return find ([...path, key], keys, data[key])
}
const get = (path, doc) =>
find([], path.split('.'), doc)
const doc =
{a: {b: {c: 'hello'},d: {c: 'sup',e: {f: 'blah blah blah'}}}}
console.log (get ('a.b.c', doc))
// [ { 'a.b.c': 'hello' } ]
console.log (get ('a.*.c', doc))
// [ { 'a.b.c': 'hello' }, { 'a.d.c': 'sup' } ]
console.log (get ('a.*', doc))
// [ { 'a.b': { c: 'hello' } },
// { 'a.d': { c: 'sup', e: { f: 'blah blah blah' } } } ]
console.log (get ('*.b', doc))
// [ { 'a.b': { c: 'hello' } } ]
Why I recommend the List monad
I do personally recommend the List monad approach as it keeps the body of the find function most clean. It also encompasses the concept of ambiguous computation and allows you to reuse that wherever you might require such a behaviour. Without using the List monad, you would rewrite the necessary code each time which adds a lot of cognitive load on the understanding of the code.
Adjust the shape of your result
The return type of your function is pretty weird. We're returning an Array of objects that only have one key/value pair. The key is the path we found the data on, and the value is the matched data.
In general, we shouldn't be using Object keys this way. How would we display the results of our match?
// get ('a.*', doc) returns
let result =
[ { 'a.b': { c: 'hello' } },
{ 'a.d': { c: 'sup', e: { f: 'blah blah blah' } } } ]
result.forEach (match =>
Object.keys (match) .forEach (path =>
console.log ('path:', path, 'value:', match[path])))
// path: a.b value: { c: 'hello' }
// path: a.d value: { c: 'sup', e: { f: 'blah blah blah' } }
What if we returned [<key>, <value>] instead of {<key>: <value>}? It's much more comfortable to work with a result in this shape. Other reasons to support this is a better shape for your data is something like Array#entries or Map#entries()
// get ('a.*', doc) returns proposed
let result =
[ [ 'a.b', { c: 'hello' } ],
[ 'a.d', { c: 'sup', e: { f: 'blah blah blah' } } ] ]
for (let [path, value] of result)
console.log ('path:', path, 'value:', value)
// path: a.b value: { c: 'hello' }
// path: a.d value: { c: 'sup', e: { f: 'blah blah blah' } }
If you agree this is a better shape, updating the code is simple (changes in bold)
// List monad version
const find = (path, [key, ...keys], data) => {
if (key === undefined)
return List ([[path.join ('.'), data]])
...
}
// native arrays version
const find = (path, [key, ...keys], data) => {
if (key === undefined)
return [[path.join ('.'), data]]
...
}
I have an object similar to the one below:
var obj1 = {
fparams: {
keys: ['a', 'b'],
pairs: {
'p': 'qwert'
}
},
qparams: {
'x': 'xyz'
}
}
And another one as:
var obj2 = {
fparams: {
keys: ['c', 'd'],
pairs: {
'q': 'yuiop'
}
},
qparams: {
'z': 'zyx'
}
}
What can I do to add the properties from obj2 object to obj1?
As I am working in Angular, I tried to use angular.merge(obj1,obj2) but it does not merge the keys array, but overwrites it with the keys value from obj2, rest of the properties get merged though.
Here's what I want in the end:
var obj2 = {
fparams: {
keys: ['a', 'b', 'c', 'd'],
pairs: {
'p': 'qwert',
'q': 'yuiop'
}
},
qparams: {
'x': 'xyz',
'y': 'zyx'
}
}
Also The angular Version I'm Using Is angular 1.5.8
Edit : Ended up using lodash , as it was much easier to work with and tons of functionalities which I was not previously aware of.
Did you consider using Lodash? It has a merge method that does exactly that.
I guess simply with pure JS and the help of the recursive function mergeObjects you can do as follows;
function mergeObjects(o1,o2){
return Object.keys(o1)
.reduce(function(r,k){
o1[k] = Array.isArray(o1[k]) ? o1[k].concat(o2[k])
: typeof o1[k] === "object" ? mergeObjects(o1[k],o2[k])
: (r = Object.assign({},o1,o2),o1[k]);
return r;
}, o1);
}
var objs = [{
fparams: {
keys : ['a', 'b'],
pairs: {
'p': 'qwert'
}
},
qparams: {
'x': 'xyz'
}
},
{
fparams: {
keys : ['c', 'd'],
pairs: {
'q': 'yuiop'
}
},
qparams: {
'z': 'zyx'
}
}],
result = objs.reduce(mergeObjects);
console.log(JSON.stringify(result,null,2));
With lodash you can use mergeWith and create desired result, so when value is array you need to concat values.
var obj1 = {"fparams":{"keys":["a","b"],"pairs":{"p":"qwert"}},"qparams":{"x":"xyz"}}
var obj2 = {"fparams":{"keys":["c","d"],"pairs":{"q":"yuiop"}},"qparams":{"z":"zyx"}}
var result = _.mergeWith(obj1, obj2, function(a, b) {
if(_.isArray(a)) return a.concat(b)
})
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.15.0/lodash.min.js"></script>
Here is my attempt at deep merge, using recursion, not sure if it will work on all data structures.
var obj1 = {"fparams":{"keys":["a","b"],"pairs":{"p":"qwert"}},"qparams":{"x":"xyz"}}
var obj2 = {"fparams":{"keys":["c","d"],"pairs":{"q":"yuiop"}},"qparams":{"z":"zyx"}}
function merge(o1, o2) {
var result = {}
for (var i in o1) {
for (var j in o2) {
if (i == j && typeof o1[i] == 'object' && typeof o2[j] == 'object') {
if (Array.isArray(o1[i]) || Array.isArray(o2[j])) {
result[i] = Array.isArray(o1[i]) ? o1[i].concat(o2[j]) : o2[j].concat(o1[i])
} else {
result[i] = Object.assign(result[i] || {}, merge(o1[i], o2[j]))
}
}
if (typeof o1[i] != 'object' || typeof o2[j] != 'object') {
result[i] = o1[i]
result[j] = o2[j]
}
}
}
return result;
}
console.log(JSON.stringify(merge(obj1, obj2), 0, 4))
try this function, I have tried to cover most of the scenarios though it might not be as good as other libraries available
var merge = function(obj1, obj2) {
for (var key in obj2) {
if (obj2.hasOwnProperty(key)) {
if (obj1[key] == null) {
obj1[key] = obj2[key];
} else if (obj1[key] instanceof Array) {
if (obj2[key] instanceof Array) {
for (var i =0; i < obj2[key].length; i++){
if (obj1[key].indexOf(obj2[key]) === -1) {
obj1[key].push(obj2[key][i]);
}
}
}
} else if (obj1[key] instanceof Object && obj2[key] instanceof Object && obj1[key].constructor == Object && obj2[key] == Object) {
merge(obj1[key], obj2[key]);
}
}
}
}
You can use it as below
merge(obj1, obj2);
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;
}
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; })