This question already has answers here:
How to deep merge instead of shallow merge?
(47 answers)
Closed 2 years ago.
let x = { "1": { "id": 1 }, "2": { "id": 1, "key": "value" } }
let y = { "2": { "id": 2 } }
let z = {
...x,
...y,
}
console.log(z);
I would like to have an output of
{ "1": { "id": 1 }, "2": { "id": 2, "key": "value" } }
the Current output is
{ "1": { "id": 1 }, "2": { "id": 2 } }
Keys will only be applies the the first level, you must go down a level.
let x = { "1": { "id": 1 }, "2": { "id": 1, "key": "value" } }
let y = { "2": { "id": 2 } }
let z = { ...x, ...y } // outer
for (let k in z) z[k] = { ...x[k], ...y[k] } // inner
console.log(z)
.as-console-wrapper { top: 0; max-height: 100% !important; }
<!-- Expected
{
"1": {
"id": 1
},
"2": {
"id": 2,
"key": "value"
}
}
-->
A more robust approach
I borrowed Lewis' example for deep-merging multiple objects and converted it to a plugin.
// Based on: https://stackoverflow.com/a/55736757/1762224
const ObjectUtils = (() => {
const { keys, freeze } = Object // sym-links
let __isObject, __merge, __coalesceByKey, __deepMerge // fwd declaration
__isObject = a => typeof a === "object" && !Array.isArray(a)
__merge = (a, b) =>
__isObject(a) && __isObject(b)
? __deepMerge(a, b)
: __isObject(a) && !__isObject(b)
? a : b
__coalesceByKey = src => (acc, key) =>
(acc[key] && src[key]
? (acc[key] = __merge(acc[key], src[key]))
: (acc[key] = src[key])) && acc
__deepMerge = (target, ...sources) =>
sources.reduce(
(acc, src) => keys(src).reduce(__coalesceByKey(src), acc), target
)
return freeze({ isObject : __isObject, deepMerge : __deepMerge })
})()
let x = { "1": { "id": 1 }, "2": { "id": 1, "key": "value" } }
let y = { "2": { "id": 2 } }
console.log(ObjectUtils.deepMerge({}, x, y));
.as-console-wrapper { top: 0; max-height: 100% !important; }
I would use lodash:
_.merge(object, [sources])
This method is like _.assign except that it recursively merges own and inherited enumerable string keyed properties of source objects into the destination object. Source properties that resolve to undefined are skipped if a destination value exists. Array and plain object properties are merged recursively. Other objects and value types are overridden by assignment. Source objects are applied from left to right. Subsequent sources overwrite property assignments of previous sources.
Here is my solution:
let x = { "1": { id: 1 }, "2": { id: 1, key: "value" } };
let y = { "2": { id: 2 } };
let z = {};
for (let [key, value] of Object.entries(x)) {
if (y.hasOwnProperty(key)) {
z[key] = {
...value,
id: y[key].id
};
} else {
z[key] = value;
}
}
console.log(z);
Related
I have input data like this:
[{
"name": "outField2",
"value": "something"
}, {
"name": "outField3[index].outField4",
"value": "something"
}, {
"name": "outField3[index].outField5",
"value": "something"
}, {
"name": "outField3[index].outField6.outField7",
"value": "something"
}]
I am trying to achieve an output like this based on substring '[index]' (i.e. if that substring is not present then that element should be an object instead of an array):
{
"outField2": "something",
"outField3[index]": [{
"outField4": "something",
"outField5": "something",
"outField6": {
"outField7": "something"
}
}]
}
My current code (below) is able to produce the outField3 as an object if there is no substring '[index]' but I'm unable to find a good solution to generate it as an array in the presence of the substring. Can someone help out? I've tried a few options but none gives me the desired result.
function doThis(item, index) {
let path = map[index].name.split(".");
if (path.length > 1) {
createNestedObject(mapOutput, path, map[index].value);
} else {
mapOutput[map[index].name] = map[index].value;
};
};
function createNestedObject(element, path, value) {
var lastElement = arguments.length === 3 ? path.pop() : false;
for (var i = 0; i < path.length; i++) {
if (path[i].includes('[index]')) {
/*some logic here to push the child elements
that do not contain [index] as an array into
the ones that contain [index]*/
} else {
element = element[path[i]] = element[path[i]] || {};
};
}
if (lastElement) element = element[lastElement] = value;
return element;
};
const map = [{
"name": "outField2",
"value": "something"
}, {
"name": "outField3[index].outField4",
"value": "something"
}, {
"name": "outField3[index].outField5",
"value": "something"
}, {
"name": "outField3[index].outField6.outField7",
"value": "something"
}];
let mapOutput = {};
map.forEach(doThis);
let mapOutputJSON = JSON.stringify(mapOutput, null, 2);
console.log(mapOutputJSON);
.as-console-wrapper { min-height: 100%!important; top: 0; }
you can do something like this
const data = [{
"name": "outField2",
"value": "something"
},
{
"name": "outField3[index].outField4",
"value": "something"
},
{
"name": "outField3[index].outField5",
"value": "something"
},
{
"name": "outField3[index].outField6.outField7",
"value": "something"
}
]
const buildObject = (paths, value, obj) => {
if (paths.length === 0) {
return value
}
const [path, ...rest] = paths
if(path.includes('[index]')) {
return {
...obj,
[path]: [buildObject(rest, value, (obj[path] || [])[0] || {})]
}
}
return {
...obj,
[path]: buildObject(rest, value, obj[path] || {})
}
}
const result = data.reduce((res, {
name,
value
}) => buildObject(name.split('.'), value, res), {})
console.log(result)
A possible generic approach which in my opinion also assigns the correct type of the OP's "outField3[index]" property (object type instead of an Array instance) is based on reduce where ...
the outer loop iterates the array of { name, value } items
by executing a single function accumulateObjectTypeFromPathAndValue where ...
this function does split each name-value into an array of object-path keys which then gets iterated by the inner reduce method where the passed object programmatically accumulates nested key-value pairs.
function accumulateObjectTypeFromPathAndValue(root, path, value) {
path
.split('.')
.reduce((obj, key, idx, arr) => {
if (!obj.hasOwnProperty(key)) {
Object.assign(obj, {
[ key ]: (idx === arr.length - 1)
? value
: {},
});
}
return obj[key];
}, root);
return root;
}
console.log(
[{
"name": "outField2",
"value": "something"
}, {
"name": "outField3[index].outField4",
"value": "something"
}, {
"name": "outField3[index].outField5",
"value": "something"
}, {
"name": "outField3[index].outField6.outField7",
"value": "something"
}].reduce((result, { name: path, value }) => {
return accumulateObjectTypeFromPathAndValue(result, path, value);
}, {})
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
The above implementation of the 2nd reducer function then could be changed according to the OP's custom array-type requirements ...
function accumulateCustomObjectTypeFromPathAndValue(root, path, value) {
path
.split('.')
.reduce((obj, key, idx, arr) => {
if (!obj.hasOwnProperty(key)) {
Object.assign(obj, {
[ key ]: (idx === arr.length - 1)
? value
: {},
});
if (key.endsWith('[index]')) {
obj[ key ] = [obj[ key ]];
}
}
return Array.isArray(obj[ key ])
//? obj[ key ].at(-1) // last item.
? obj[ key ][obj[ key ].length - 1] // last item.
: obj[ key ];
}, root);
return root;
}
console.log(
[{
"name": "outField2",
"value": "something"
}, {
"name": "outField3[index].outField4",
"value": "something"
}, {
"name": "outField3[index].outField5",
"value": "something"
}, {
"name": "outField3[index].outField6.outField7",
"value": "something"
}].reduce((result, { name: path, value }) => {
return accumulateCustomObjectTypeFromPathAndValue(result, path, value);
}, {})
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
I'm trying to create two (2) recursive functions to "loop" over an array of objects like below. I think the two functions are "similar" but they do two different things.
Function 1 should update the object - which could be every field in the "found" object and return the "new" array of objects, so the function needs to identify the appropriate object by .id
Function 2 needs to identify the appropriate object by .id BUT to delete that object and again return the "new" array of objects.
I've tried a number of ways (below the array of objects) - but to no avail, I cannot get the new object to return.
To note even if each object has varying/different keys, there will always be an .id key -
[
{
"type":"section",
"name":"Section 1",
"hassection":[
{
"type":"section",
"id":1,
"name":"Section 1 child section 1",
"hasMenuItem":
[
{
"type":"item",
"id":2,
"name":"Item 1",
"prices":
{
"type":"price",
"price":"15.95"
},
"description":"Blah Blah..."
},{
"type":"item",
"id":3,"name":
"Item 2",
"prices":[
{
"type":"price",
"price":"24.95"
},{
"type":"price",
"price":"13.95"
}
],
"description":"Blah Blah..."
}
]
},{
"type":"section",
"id":4,
"name":"Section 1 child section 2",
"hasitem":[
{
"type":"item",
"name":"Item 3",
"prices":
{
"type":"price","price":"14.50"
},
"description":"Blah Blah..."
},{
"type":"item",
"id":5,
"name":"Item 4",
"prices":
{
"type":"price",
"price":"14.50"
},
"description":"Blah Blah..."
}
]
},
]},{
"type":"section",
"name":"Section 2",
"hassection":[
{
"type":"section",
"id":6,
"name":"Section 2 child section 1",
"hasitem":[
{
"type":"item",
"id":7,
"name":"Item 5",
"prices":
{
"type":"price",
"price":"15.95"
},
"description":"Blah Blah..."
},
{
"type":"item",
"id":8,
"name":"Item 6",
"prices":
{
"type":"price",
"price":"13.95"
},
"description":"Blah Blah..."
}
]
}
]}
]
My update function
function updateNestedObj(obj,updates) {
const updateToApply = updates.find(upd => upd.id === obj.id);
if (updateToApply) {
// UPDATE THE OBJ
}
for(let k in obj) {
if (typeof(obj[k]) === 'object') {
// LOOP THROUGH THE OBJECT
updateNestedObj(obj[k], updates);
}
}
return updateToApply
}
My Delete function
function deleteNestedObj(obj, updates) {
const updateToApply = updates.find(upd => upd.id === obj.id);
if (updateToApply) {
delete upd;
}
for(let k in obj) {
if (typeof(obj[k]) === 'object') {
deleteNestedObj(obj[k], updates);
}
}
}
I just cannot fathom out how to "work them" - thanks in advance, any help much appreciated.
generics
Let's start with a immutable update(t, func) that takes a value of any type, t, and a callable updater function, func. func transforms t per the caller's specified return value. If no value is returned, ie undefined, then update will remove that value from the tree -
function update(t, func) {
switch (t?.constructor) {
case Object:
return Object.entries(t).reduce((r, [k, v]) => {
const newValue = update(func(v), func)
if (newValue !== undefined) r[k] = newValue
return r
}, {})
case Array:
return t.flatMap(v => {
const newValue = update(func(v), func)
return newValue === undefined ? [] : [newValue]
})
default:
return t
}
}
Immutable remove(t, func) can be defined as a specialization of update -
function remove(t, func) {
return update(t, v => Boolean(func(v)) ? undefined : v)
}
special forms
The functions can be further specialized to match your particular needs. updateWithObj(t, obj) will recursively update t where a node's id matches obj.id -
function updateWithObj(t, obj) {
return update(t, v => v.id == obj.id ? {...v, ...obj} : v)
}
Likewise removeWithObj(t, obj) recursively removes from t where a node's id matches obj.id -
function removeWithObj(t, obj) {
return remove(t, v => v.id == obj.id)
}
examples
Let's create some sample data. For what it's worth, update doesn't care whether it is an array of elements, [...] or a single object, {...} -
const data = [
{id: 1, data: 50 },
{id: 2, data: {id: 3, data: "foo"}},
{id: 4, data: [{id: 5, data: 3.141}, {id: 6, data: {id: 7, data: "bar"}}]}
]
We'll start with a simple update on obj.id == 1. Note the existing data attribute remains in tact and a new ok attribute is added. All other nodes remain unchanged -
console.log(updateWithObj(data, {id: 1, ok: "✅"}))
[
{
"id": 1,
"data": 50, // 👍🏽 remains unchanged
"ok": "✅" // 👍🏽 updated
},
{
"id": 2,
"data": {
"id": 3,
"data": "foo"
}
},
{
"id": 4,
"data": [
{
"id": 5,
"data": 3.141
},
{
"id": 6,
"data": {
"id": 7,
"data": "bar"
}
}
]
}
]
Here we see a deeply nested update with obj.id == 7. Note the data attribute for this node is updated and a new ok attribute is added -
console.log(updateWithObj(data, {id: 7, data: 0.123, ok: "✅"}))
[
{
"id": 1,
"data": 50
},
{
"id": 2,
"data": {
"id": 3,
"data": "foo"
}
},
{
"id": 4,
"data": [
{
"id": 5,
"data": 3.141
},
{
"id": 6,
"data": {
"id": 7,
"data": 0.123, // 👍🏽 updated
"ok": "✅" // 👍🏽 updated
}
}
]
}
]
Now let's see removal using removeWithObj. Notice obj.id == 6 is removed along with its descendants -
console.log(removeWithObj(data, {id: 6}))
[
{
"id": 1,
"data": 50
},
{
"id": 2,
"data": {
"id": 3,
"data": "foo"
}
},
{
"id": 4,
"data": [
{
"id": 5,
"data": 3.141
}
// 👍🏽 node removed
]
}
]
live demo
Here's a demo you can run in your own browser -
function update(t, func) {
switch (t?.constructor) {
case Object:
return Object.entries(t).reduce((r, [k, v]) => {
const newValue = update(func(v), func)
if (newValue !== undefined) r[k] = newValue
return r
}, {})
case Array:
return t.flatMap(v => {
const newValue = update(func(v), func)
return newValue === undefined ? [] : [newValue]
})
default:
return t
}
}
function remove(t, func) {
return update(t, v => Boolean(func(v)) ? undefined : v)
}
function updateWithObj(t, obj) {
return update(t, v => v.id == obj.id ? {...v, ...obj} : v)
}
function removeWithObj(t, obj) {
return remove(t, v => v.id == obj.id)
}
const data = [
{id: 1, data: 50 },
{id: 2, data: {id: 3, data: "foo"}},
{id: 4, data: [{id: 5, data: 3.141}, {id: 6, data: {id: 7, data: "bar"}}]}
]
console.log(updateWithObj(data, {id: 1, ok: "✅"}))
console.log(updateWithObj(data, {id: 7, data: 0.123, ok: "✅"}))
console.log(removeWithObj(data, {id: 6}))
.as-console-wrapper { min-height: 100%; top: 0; }
why undefined?
#Scott's comment draws attention to use of undefined as the mechanism for removal. I have always advocated for the programmer to reserve the right to use undefined for her/his particular needs. If a user gives undefined to a program, they can expect undefined behavior. For update, the undefined value is explicitly used to make a value not defined, or not there, ie remove it.
Other reasons support this choice. In most cases, an object with an explicitly undefined key behaves the same as one without the key. If the user really wants a var/key to be present but not yet set to a value, this is the perfect use of null -
const a = { foo: undefined } // foo is defined, but also not defined ??
const b = {} // does not have foo
console.log(a.foo, b.foo) // same behavior
// undefined undefined
JSON considers an undefined as "not defined" and so removes it when serializing an object -
const o = { a: undefined, b: null, c: false, d: 0, e: "" }
const j = JSON.stringify(o)
// "a" is not defined, so it's not serialized
console.log(j)
// looking for "a"? it's not defined :D
console.log(JSON.parse(j).a)
// undefined input gives undefined output. it's not defined :D
console.log(JSON.stringify(undefined))
ReScript plainly encodes None (ie "no value") as undefined in its Option module. Any Some(value) is represented as value -
// rescript
let foo = Some(1)
switch foo {
| Some(z) => Js.log(z)
| None => Js.log("no value")
}
// compiled javascript
var foo = 1;
if (foo !== undefined) {
console.log(foo);
} else {
console.log("no value");
}
explicit symbol
Maybe none of that convinces you and your fragile program still depends on having that undefined appear in the output. An explicit none sentinel can be used to signal to update that a particular value should be removed -
const none = Symbol() // ✅ removal sentinel
function update(t, func) {
switch (t?.constructor) {
case Object:
return Object.entries(t).reduce((r, [k, v]) => {
const newValue = update(func(v), func)
if (newValue !== none) r[k] = newValue // ✅
return r
}, {})
case Array:
return t.flatMap(v => {
const newValue = update(func(v), func)
return newValue === none ? [] : [newValue] // ✅
})
default:
return t
}
}
function remove(t, func) {
return update(t, v => Boolean(func(v)) ? none : v) // ✅
}
First let me break down the data:
I have an array that contains 3 elements...
Each Element is an object with name and arrayOfJSON as keys...
Inside arrayOfJSON there could be any number of JSON strings as elements...
I need to capture the position where Alex#gmail occurs for both the array mess and arrayOfJSON
Result Should Be:
position_of_mess = [0,2]
position_of_arrayOfJSON_for_position_of_mess_0 = [0]
position_of_arrayOfJSON_for_position_of_mess_2 = [1]
What I'm trying at the moment:
For loop through mess, for loop through arrayOfJSON , and JSON.parse() for Alex#gmail.
going to take me a few mins to update.
If y'all think it can be done without a for-loop let me know.
Update: almost there
mess = [{
"name": "user1",
"arrayOfJSON": `[{"email":"Alex#gmail","hobby":"coding"},{"email":"bob#gmail","hobby":"coocking"}]`
},
{
"name": "user2",
"arrayOfJSON": `[{"email":"Chris#gmail","hobby":"coding"},{"email":"bob#gmail","hobby":"coocking"}]`
},
{
"name": "user3",
"arrayOfJSON": `[{"email":"bob#gmail","hobby":"coocking"},{"email":"Alex#gmail","hobby":"coding"}]`
}
]
console.log(mess)
for (i = 0; i < mess.length; i++) {
console.log(JSON.parse(mess[i].arrayOfJSON))
for (m = 0; m < (JSON.parse(mess[i].arrayOfJSON)).length; m++) {
console.log("almost")
console.log((JSON.parse(mess[i].arrayOfJSON))[m])
}
}
mess = [{
"name": "user1",
"arrayOfJSON": `[{"email":"Alex#gmail","hobby":"coding"},{"email":"bob#gmail","hobby":"coocking"}]`
},
{
"name": "user2",
"arrayOfJSON": `[{"email":"Chris#gmail","hobby":"coding"},{"email":"bob#gmail","hobby":"coocking"}]`
},
{
"name": "user3",
"arrayOfJSON": `[{"email":"bob#gmail","hobby":"coocking"},{"email":"Alex#gmail","hobby":"coding"}]`
}
]
console.log(mess)
holdMessPosition = []
for (i = 0; i < mess.length; i++) {
var pos = (JSON.parse(mess[i].arrayOfJSON)).map(function(e) {
return e.email;
})
.indexOf("Alex#gmail");
console.log("user position is " + pos);
if (pos !== -1) {
holdMessPosition.push(i)
}
}
console.log(holdMessPosition)
Parse your data
You want to be able to access keys inside the inner object "string"
Traverse your data
While visiting key-value pairs, build a scope thet you can later return
// Adapted from: https://gist.github.com/sphvn/dcdf9d683458f879f593
const traverse = function(o, fn, scope = []) {
for (let i in o) {
fn.apply(this, [i, o[i], scope]);
if (o[i] !== null && typeof o[i] === "object") {
traverse(o[i], fn, scope.concat(i));
}
}
}
const mess = [{
"name": "user1",
"arrayOfJSON": `[{"email":"Alex#gmail","hobby":"coding"},{"email":"bob#gmail","hobby":"coocking"}]`
}, {
"name": "user2",
"arrayOfJSON": `[{"email":"Chris#gmail","hobby":"coding"},{"email":"bob#gmail","hobby":"coocking"}]`
}, {
"name": "user3",
"arrayOfJSON": `[{"email":"bob#gmail","hobby":"coocking"},{"email":"Alex#gmail","hobby":"coding"}]`
}];
// Parse...
mess.forEach(item => {
if (item.arrayOfJSON) {
item.arrayOfJSON = JSON.parse(item.arrayOfJSON);
}
});
traverse(mess, (key, value, scope) => {
if (value === 'Alex#gmail') {
console.log(
`Position: mess[${scope.concat(key).map(k => isNaN(k) ? `'${k}'` : k).join('][')}]`
);
}
});
.as-console-wrapper {
top: 0;
max-height: 100% !important;
}
I'm trying to order an array of object of objects based on the object's key.
How do I go about sorting a JSON Object that's already in ascending order? Please see below.
I've tried to convert the data value object into its own array, then sort it that way, but I'm getting syntax errors.
var object = [
A1: {
errors: {}
otherData: {}
data: {
"1": {
"name": "Ashley",
},
"2": {
"name": "Cardiff",
},
"3": {
"name": "Reading",
}
}},
A2: {
errors: {}
otherData: {}
data: {
"4": {
"name": "Susan",
},
"5": {
"name": "Bee",
},
"6": {
"name": "Bob",
}
}}];
I want it to be:
var object = [
A1: {
errors: {}
otherData: {}
data: {
"3": {
"name": "Reading",
},
"2": {
"name": "Cardiff",
},
"1": {
"name": "Ashley",
}
}},
A2: {
errors: {}
otherData: {}
data: {
"6": {
"name": "Bob",
},
"5": {
"name": "Bee",
},
"4": {
"name": "Susan",
}
}}];
If I understand correctly you want to sort the in alphabetical order by first letter. This is kind of out there but it should do what you're looking for
const arr1 = object.map(function(o) {
return Object.values(o[Object.keys(o)].data).sort((a, b) => a - b);
})
I'll try to explain what's happening here. map is iterating over each object in the array and return a brand new array. o[Object.keys(o)].data is selecting A1 and A2 data keys. Then we're saying we want the values of the objects in those data objects with the surrounds Object.keys() which is giving us the names. From there we're just calling sort and giving it the callback.
const sortByField = (field, isRevered = false, primerFn) => {
if (field) {
var key = primerFn ? (x) => primerFn(x[field]) : (x) => x[field];
isRevered = !isRevered ? 1 : -1;
return (a, b) => {
/*eslint-disable */
return a = key(a), b = key(b), isRevered * ((a > b) - (b > a));
/*eslint-enable */
}
}
else {
return (a, b) => {
return isRevered ? a < b : a > b;
}
}
}
var dataToSort = {
A1: {
errors: {},
otherData: {},
data: {
"1": { "name": "Ashley", },
"2": { "name": "Cardiff", },
"3": { "name": "Reading", }
}
},
A2: {
errors: {},
otherData: {},
data: {
"4": { "name": "Susan", },
"5": { "name": "Bee", },
"6": { "name": "Bob", }
}
}
};
const sortObjectByKeys = (obj) => {
let values = [];
let keys = [];
Object.keys(obj).forEach(key => {
keys.push(key);
values.push(obj[key])
})
values.sort(sortByField("name", true, (value) => value.toLowerCase()));
let sortedObject = {};
values.forEach((value, index) => {
sortedObject[keys[index]] = value;
})
return sortedObject;
}
let sortedData = {};
Object.keys(dataToSort).forEach(dataKey => {
sortedData[dataKey] = {
...dataToSort[dataKey],
data: sortObjectByKeys(dataToSort[dataKey].data)
}
})
console.log(JSON.stringify(sortedData));
I have an array of objects which I am trying to loop over and check for a common key if it exists for all objects. if the specific key does not exist for all objects I return false.
Here is my code
var x = [{
"item": "alpha",
"value": "red"
}, {
"item": "beta",
"value": "blue"
}, {
"item": "beta",
"value": "gama"
}]
function test(obj) {
var count = 0;
var out = false;
for (var i = 0; i < obj.length; i++) {
if (obj[i].hasOwnProperty('value')) {
count = i;
}
}
if (count == obj.length) {
out = true
}
}
console.log(test(x))
I am getting undefined. Cant figure out what am I missing here
A really simple way to do this is to use Array#every like this
var x = [{
"item": "alpha",
"value": "red"
}, {
"item": "beta",
"value": "blue"
}, {
"item": "beta",
"value": "gama"
}]
function test(obj) {
return obj.every(a => a.hasOwnProperty("value"));
}
console.log(test(x))
Update
As rightfully mentioned by this comment first.
Here can be the simple solution for this object:
var x = [{
"item": "alpha",
"value": "red"
}, {
"item": "beta",
"value": "blue"
}, {
"item": "beta",
"value": "gama"
}];
function test(obj) {
var keyCount = 0;
obj.forEach(function (item, index) {
item.hasOwnProperty('value') && ++keyCount;
});
return keyCount == obj.length;
}
console.log(test(x));
Here is my implementation, which finds every matching key, even nested keys, given a set of objects:
function recurse_obj(obj, cb, _stack = []) {
for (var k in obj) {
cb(k, obj[k], _stack);
if (obj.hasOwnProperty(k) && (obj[k] instanceof Object)) {
_stack.push(k);
recurse_obj(obj[k], cb, _stack);
_stack.pop();
}
}
}
function obj_all_keys(obj) {
var tmp = [];
recurse_obj(obj, (k, v, stack) => {
var ext = (stack.length) ? "." : "";
tmp.push(stack.join(".").concat(ext, k));
});
return tmp;
}
function key_intersection(...objs) {
var lookup = {};
objs.forEach(o => {
obj_all_keys(o).forEach(k => {
if (k in lookup === false)
lookup[k] = 0;
lookup[k]++;
});
});
for (var k in lookup)
if (lookup[k] !== objs.length)
delete lookup[k];
return lookup;
}
Here is the calling code:
var me = { name: { first: "rafael", last: "cepeda" }, age: 23, meta: { nested: { foo: { bar: "hi" } } } };
console.log(key_intersection(me, { name: { first: "hi" } }));
Output: { name: 2, 'name.first': 2 }
The object returned includes only the keys that are found in all the objects, the set intersection, the counts are from book-keeping, and not removed in the callee for performance reasons, callers can do that if need be.
Keys that are included in other nested keys could be excluded from the list, because their inclusion is implied, but I left them there for thoroughness.
Passing a collection (array of objects) is trivial:
key_intersection.apply(this, collection);
or the es6 syntax:
key_intersection(...collection);