Recursive "merge" or "extend" with Ramda? - javascript

I'm trying to find an equivalent function to Lodash's merge using Ramda that does a recursive object key-based "merge" or "extend". The behavior is similar to the following:
let merged = R.someMethod(
{ name: 'Matt', address: { street: 'Hawthorne', number: 22, suffix: 'Ave' }},
{ address: { street: 'Pine', number: 33 }}
);
console.log(merged);
// => { name: 'Matt', address: { street: 'Pine', number: 33, suffix: 'Ave' }}
I noticed in the following pull request that R.set was briefly introduced, but then rolled back soon thereafter. Has this functionality been captured by the Ramda library since?
Is this functionality available in Ramda?

A relatively simple recursive function can be created using R.mergeWith.
function deepMerge(a, b) {
return (R.is(Object, a) && R.is(Object, b)) ? R.mergeWith(deepMerge, a, b) : b;
}
deepMerge({ name: 'Matt', address: { street: 'Hawthorne', number: 22, suffix: 'Ave' }},
{ address: { street: 'Pine', number: 33 }});
//=> {"address": {"number": 33, "street": "Pine", "suffix": "Ave"}, "name": "Matt"}

Ramda does not include such a function at the moment.
There have been several attempts to create one, but they seem to founder on the notion of what's really required of such a function.
Feel free to raise an issue if you think it's worth adding.
Update
(Two years later.) This was eventually added, in the form of several functions: mergeDeepLeft, mergeDeepRight, mergeDeepWith, and mergeDeepWithKey.

Ramda now has several merge functions: mergeDeepLeft, mergeDeepRight, mergeDeepWith, mergeDeepWithKey.

const { unapply, mergeDeepRight, reduce } = R
const mergeDeepRightAll = unapply(reduce(mergeDeepRight, {}))
console.log(mergeDeepRightAll({a:1, b: {c: 1}},{a:2, d: {f: 2}},{a:3, b: {c:3}}))
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>

from scratch
Newer functions in the Ramba library mean you don't have to do this on your own, but what if the maintainers never go around to it? You don't want to be stuck waiting on someone else to write your code when you need a feature or behavior right now.
Below, we implement our own recursive merge
const isObject = x =>
Object (x) === x
const merge = (left = {}, right = {}) =>
Object.entries (right)
.reduce
( (acc, [ k, v ]) =>
isObject (v) && isObject (left [k])
? { ...acc, [k]: merge (left [k], v) }
: { ...acc, [k]: v }
, left
)
Our merge function also works generically and accepts any two objects as input.
const x =
{ a: 1, b: 1, c: 1 }
const y =
{ b: 2, d: 2 }
console.log (merge (x, y))
// { a: 1, b: 2, c: 1, d: 2 }
In the event each object contains a property whose value is also an object, merge will recur and merge the nested objects as well.
const x =
{ a: { b: { c: 1, d: 1 } } }
const y =
{ a: { b: { c: 2, e: 2 } }, f: 2 }
console.log (merge (x, y))
// { a: { b: { c: 2, d: 1, e: 2 } }, f: 2 }
arrays are people too
To support arrays in merge, we introduce a mutation helper mut which assigns a [ key, value ] pair to a given object, o. Arrays are considered objects too, so we can update both arrays and objects using the same mut function
Note, Ramda's merging functions do not attempt to merge arrays. The primary advantage to writing your own functions is you can easily augment their behavior to meet your program's ever-evolving requirements.
const mut = (o, [ k, v ]) =>
(o [k] = v, o)
const merge = (left = {}, right = {}) =>
Object.entries (right)
.map
( ([ k, v ]) =>
isObject (v) && isObject (left [k])
? [ k, merge (left [k], v) ]
: [ k, v ]
)
.reduce (mut, left)
Shallow merges work as expected
const x =
[ 1, 2, 3, 4, 5 ]
const y =
[ 0, 0, 0 ]
const z =
[ , , , , , 6 ]
console.log (merge (x, y))
// [ 0, 0, 0, 4, 5 ]
console.log (merge (y, z))
// [ 0, 0, 0, <2 empty items>, 6 ]
console.log (merge (x, z))
// [ 1, 2, 3, 4, 5, 6 ]
And deep merges too
const x =
{ a: [ { b: 1 }, { c: 1 } ] }
const y =
{ a: [ { d: 2 }, { c: 2 }, { e: 2 } ] }
console.log (merge (x, y))
// { a: [ { b: 1, d: 2 }, { c: 2 }, { e: 2 } ] }
variadic merge
Maybe we want a merge function that is not limited to two inputs; mergeAll
const Empty =
{}
const mergeAll = (first = Empty, ...rest) =>
first === Empty
? first
: merge (first, mergeAll (...rest))
mergeAll ({ a: 1 }, { b: 2 }, { c: 3 })
// { a: 1, b: 2, c: 3 }
This answer is an excerpt from another question: How to compare two objects and get key-value pairs of their differences?

Related

How can I unite multiple objects to add their keys?

Implement the sumObjects function, which takes an unlimited number of objects and returns an object that combines all the objects entered.
Notes:
All object properties will have numeric values only
If the object keys match, the values of the corresponding keys are summed
The function always returns an object
The numbers in the object can be positive or negative
You can use the rest operator to collect arguments passed to a function in one array
Examples:
const obj = {};
sumObjects() === {}
sumObjects(obj) === {}
and
const first = {a: 2, b: 4};
const second = {a: 2, b: 10};
const third = {d: -5};
sumObjects(first) === {a: 2, b: 4}
sumObjects(first, third) === {a: 2, b: 4, d: -5}
sumObjects(first, second, third) === {a: 4, b: 14, d: -5}
This is my code.
function sumObjects(...params) {
let C = Object.fromEntries(
Object.keys(params[0])
.concat(Object.keys(params[1]))
.map(k => [k,
(params[0][k] || 0) + (params[1][k])
])
)
return C
}
I don't know how to add all these objects into one.
The bottom line is that I need to combine all the objects, but I just don't know how to do this, I can't find anything.
You can iterate over the param objects using .reduce and for each object, set/update its properties in acc:
function sumObjects(...params) {
return params.reduce((acc,item) => {
Object.entries(item).forEach(([property,value]) => {
const prev = acc[property];
acc[property] = prev ? prev+value : value;
});
return acc;
}, {});
}
const first = { a: 2, b: 4 };
const second = { a: 2, b: 10 };
const third = { d: -5 };
console.log( sumObjects(first) );
console.log( sumObjects(first, third) );
console.log( sumObjects(first, second, third) );
You could reduce the array of objects and iterate the entries of a single object.
const
sumObjects = (...objects) => objects.reduce((r, o) => {
Object.entries(o).forEach(([k, v]) => r[k] = (r[k] ||0) + v);
return r;
}, {}),
first = { a: 2, b: 4 },
second = { a: 2, b: 10 },
third = { d: -5 };
console.log(sumObjects(first)); // {a: 2, b: 4}
console.log(sumObjects(first, third)); // {a: 2, b: 4, d: -5}
console.log(sumObjects(first, second, third)); // {a: 4, b: 14, d: -5}
.as-console-wrapper { max-height: 100% !important; top: 0; }

How to create combinations of inner array elements inside a main n-length array?

This is the result I'm looking for:
Input : [
[
{a: [v]},
{b: [v]}
],
[
{c: [v]},
{d: [v]}
]
]
Output : [
{
a: [v],
c: [v]
},
{
a: [v],
d: [v]
},
{
b: [v],
c: [v]
},
{
b: [v],
d: [v]
}
]
First it is an array of arrays made with objects. Then it turns to an array of objects. Each combination can have one element of each original array only. My attempt is the following:
arr.forEach((d, i) => {
d.forEach((l, j) => {
temp[j] ? temp[j].push(l) : temp[j] = [l];
});
});
But I haven't figured out yet how to do it.

update nested json object using recursion in JavaScript

I'm trying to create an updated object from an existing object.
The sample object is:
// sample object
const testObj = {
a: 1,
b: {
c: 2,
d: {
e: 3,
f: {
g: 4
}
}
}
};
I want to create a new object from the above object with some concatenation of each value:
// expected object
const expectedObject= {
a: '1 a',
b: {
c: '2 a',
d: {
e: '3 a',
f: {
g: '4 a'
}
}
}
};
here is my sample code:
let expectedObject = {};
const newObject = object => {
Object.entries(object).forEach(([key, value]) => {
if (typeof value === "object") {
Object.keys(value).map(key => {
value[key] = value[key] + " a";
return value;
});
expectedObject[key] = value;
//return newTest;
} else {
expectedObject[key] = value;
return expectedObject;
}
});
return expectedObject;
};
console.log(newObject(testObj));
the outcome in console is:
{a: 1, b: {…}}
a: 1
b:
c: "2 a"
d: "[object Object] a"
__proto__: Object
__proto__: Object
I wanted to use recursion here and also tried it but no luck.
any help, thanks?
You could get a new object my mapping changed values and creating new objects.
function map(object, fn) {
return Object.fromEntries(Object
.entries(object)
.map(([k, v]) => [k, v && typeof v === 'object' ? map(v, fn) : fn(v)])
);
}
var object = { a: 1, b: { c: 2, d: { e: 3, f: { g: 4 } } } },
result = map(object, v => v + ' a');
console.log(result);
If you have arrays inside, you could add a check in advance and map the values.
const
map = fn => {
const iter = v => v && typeof v === 'object'
? Array.isArray(v)
? v.map(iter)
: Object.fromEntries(Object.entries(v).map(([k, v]) => [k, iter(v, fn)]))
: fn(v);
return iter;
};
var object = { a: 1, b: { c: 2, d: { e: 3, f: { g: 4, k: [5, 6] } } } },
addA = map(v => v + ' a'),
result = addA(object);
console.log(result);
This is simply a refactoring of the answer from #user633183. I like that approach a lot, but think it can be simplified by extracting two more reusable functions. This started as a comment on that answer, but I thought it would be better to be explicit.
const map = (f) => (a) =>
a.map(f)
const mapObj = (f) => (o) =>
Object .entries (o) .reduce ( (a, [k, v] ) => ({ ...a, [k]: f(v) }), {})
const traverse = (f) => (t) =>
Array.isArray(t)
? map (traverse (f)) (t)
: Object(t) === t
? mapObj (traverse (f)) (t)
: f (t)
const input =
{ a: [ 1, 11, 111 ], b: { c: 2, d: { e: [ 3, { f: { g: 4 } } ] } } }
const output =
traverse(x => `${x} a`) (input)
console.log(output)
mapObj can be written in many different ways. Here are two alternatives:
const mapObj = (f = identity) => (o = {}) =>
Object .fromEntries (Object .entries (o) .map (([ k, v ]) => [ k, f (v) ]))
const mapObj = (f = identity) => (o = {}) =>
Object .assign .apply (null, Object .entries (o) .map (([ k, v ]) => ({ [k]: f (v)
Here's an approach using a modification of the original code to demonstrate what needed to be changed in order to make it work. You had some things switched up reading the value and setting the new one. Also I'm using the spread operator to clone the object before modifying it.
const testObj = {
a: 1,
b: {
c: 2,
d: {
e: 3,
f: {
g: 4
}
}
}
};
const newObject = object => {
const clonedObj = { ...object };
const entries = Object.entries(clonedObj);
entries.forEach(([key, value]) => {
if (typeof value === "object") {
clonedObj[key] = newObject(value);
} else {
clonedObj[key] = value + " a";
}
});
return clonedObj;
};
console.log(newObject(testObj));
console.log(testObj); // prove that the original object hasn't changed
Here's a simple recursive technique. It is similar to Nina's but it preserves arrays, if present in the structure.
If the input, t, is an array, create a new array by traversing each array value, v, with the traversing function, f
(inductive) Otherwise t is not an array. If t is an object, create a new object from key value pairs, [ k, v ], by traversing each value, v, with the traversing function, f
(inductive) Otherwise t is not an array and t is not an object. This means t is either a primitive value, such as string, number, or null
Numbered comments below correspond to the explanation above -
const identity = x =>
x
const traverse = (f = identity, t = {}) =>
Array.isArray(t) // 1
? Array.from(t, v => traverse(f, v))
: Object(t) === t // 2
? Object.fromEntries(Object.entries(t).map(([ k, v ]) => [ k, traverse(f, v) ]))
: f (t) // 3
const input =
{ a: [ 1, 11, 111 ], b: { c: 2, d: { e: [ 3, { f: { g: 4 } } ] } } }
const output =
traverse(x => `${x} a`, input)
console.log(output)
Here is a solution using object-scan. It works by building the solution at the same time as the input is traversed.
// const objectScan = require('object-scan');
const testObj = { a: 1, b: { c: 2, d: { e: 3, f: { g: 4 } } } };
const cloneAndModify = (obj) => objectScan(['**'], {
breakFn: ({ property, value, isLeaf, context }) => {
if (property === undefined) {
return;
}
const ref = context[context.length - 1];
if (!(property in ref)) {
ref[property] = isLeaf ? `${value} a` : {};
}
context.push(ref[property]);
},
filterFn: ({ context }) => {
context.pop();
}
})(obj, [{}])[0];
const r = cloneAndModify(testObj);
console.log(r);
// => { b: { d: { f: { g: '4 a' }, e: '3 a' }, c: '2 a' }, a: '1 a' }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.7.1"></script>
Disclaimer: I'm the author of object-scan

Key value pair intersection of an array of objects

I would like to know if there is a way to find the intersection of a key value pair in an array of objects. Let's say you have an array of three objects which all have the same keys like this :
arrayOfObj = [
{
"a": 1,
"b": "stringB"
"c": {"c1":1,
"c2": "stringC2"
}
},
{
"a": 1,
"b": "stringBdiff"
"c": {"c1":1,
"c2": "stringC2"
}
},
{
"a": 1,
"b": "stringB"
"c": {"c1":1,
"c2": "stringC2"
}
}
]
I would like to find the common key value pairs of the three objects:
output= [
{"a":1},
{"c": {"c1":1,
"c2":"stringC2"
}
}
]
This is what I have done so far, it works but not on nested objects. I would like to know if there is a more elegant way to do it and one that could work on nested object as well.
let properties;
let commonFound = false;
let notCommonFound = false;
const commonValues = [];
let value;
const initialArray = [{
"a": 2,
"b": "stringB",
"c": {
"c1": 1,
"c2": "stringC2"
}
},
{
"a": 1,
"b": "stringB",
"c": {
"c1": 2,
"c2": "stringC2"
}
},
{
"a": 1,
"b": "stringB",
"c": {
"c1": 2,
"c2": "stringC2"
}
}
];
const commonStorage = [];
const reference = initialArray[0];
properties = Object.keys(reference);
properties.forEach((property) => {
for (let i = 0; i < initialArray.length; i++) {
commonFound = false;
notCommonFound = false;
for (let j = 0; j <i ; j++) {
if (initialArray[i][property] === initialArray[j][property]) {
commonFound = true;
value = initialArray[i][property];
}
else {
notCommonFound = true;
value = [];
}
}
}
if (commonFound && !notCommonFound) {
commonStorage.push({[property] : value});
}
});
console.log(commonStorage);
Before we implement intersect we'll first look at how we expect it to behave –
console.log
( intersect
( { a: 1, b: 2, d: 4 }
, { a: 1, c: 3, d: 5 }
)
// { a: 1 }
, intersect
( [ 1, 2, 3, 4, 6, 7 ]
, [ 1, 2, 3, 5, 6 ]
)
// [ 1, 2, 3, <1 empty item>, 6 ]
, intersect
( [ { a: 1 }, { a: 2 }, { a: 4, b: 5 }, ]
, [ { a: 1 }, { a: 3 }, { a: 4, b: 6 }, ]
)
// [ { a: 1 }, <1 empty item>, { a: 4 } ]
, intersect
( { a: { b: { c: { d: [ 1, 2 ] } } } }
, { a: { b: { c: { d: [ 1, 2, 3 ] } } } }
)
// { a: { b: { c: { d: [ 1, 2 ] } } } }
)
Challenging problems like this one are made easier by breaking them down into smaller parts. To implement intersect we will plan to merge two calls to intersect1, each contributing one side of the computed result –
const intersect = (left = {}, right = {}) =>
merge
( intersect1 (left, right)
, intersect1 (right, left)
)
Implementing intersect1 is remains relatively complex due to the need to support both objects and arrays – the sequence of map, filter, and reduce helps maintain a flow of the program
const intersect1 = (left = {}, right = {}) =>
Object.entries (left)
.map
( ([ k, v ]) =>
// both values are objects
isObject (v) && isObject (right[k])
? [ k, intersect (v, right[k]) ]
// both values are "equal"
: v === right[k]
? [ k, v ]
// otherwise
: [ k, {} ]
)
.filter
( ([ k, v ]) =>
isObject (v)
? Object.keys (v) .length > 0
: true
)
.reduce
( assign
, isArray (left) && isArray (right) ? [] : {}
)
Lastly we implement merge the same way we did in the other Q&A –
const merge = (left = {}, right = {}) =>
Object.entries (right)
.map
( ([ k, v ]) =>
isObject (v) && isObject (left [k])
? [ k, merge (left [k], v) ]
: [ k, v ]
)
.reduce (assign, left)
The final dependencies –
const isObject = x =>
Object (x) === x
const isArray =
Array.isArray
const assign = (o, [ k, v ]) =>
(o [k] = v, o)
Verify the complete program works in your browser below –
const isObject = x =>
Object (x) === x
const isArray =
Array.isArray
const assign = (o, [ k, v ]) =>
(o [k] = v, o)
const merge = (left = {}, right = {}) =>
Object.entries (right)
.map
( ([ k, v ]) =>
isObject (v) && isObject (left [k])
? [ k, merge (left [k], v) ]
: [ k, v ]
)
.reduce (assign, left)
const intersect = (left = {}, right = {}) =>
merge
( intersect1 (left, right)
, intersect1 (right, left)
)
const intersect1 = (left = {}, right = {}) =>
Object.entries (left)
.map
( ([ k, v ]) =>
isObject (v) && isObject (right[k])
? [ k, intersect (v, right[k]) ]
: v === right[k]
? [ k, v ]
: [ k, {} ]
)
.filter
( ([ k, v ]) =>
isObject (v)
? Object.keys (v) .length > 0
: true
)
.reduce
( assign
, isArray (left) && isArray (right) ? [] : {}
)
console.log
( intersect
( { a: 1, b: 2, d: 4 }
, { a: 1, c: 3, d: 5 }
)
// { a: 1 }
, intersect
( [ 1, 2, 3, 4, 6, 7 ]
, [ 1, 2, 3, 5, 6 ]
)
// [ 1, 2, 3, <1 empty item>, 6 ]
, intersect
( [ { a: 1 }, { a: 2 }, { a: 4, b: 5 }, ]
, [ { a: 1 }, { a: 3 }, { a: 4, b: 6 }, ]
)
// [ { a: 1 }, <1 empty item>, { a: 4 } ]
, intersect
( { a: { b: { c: { d: [ 1, 2 ] } } } }
, { a: { b: { c: { d: [ 1, 2, 3 ] } } } }
)
// { a: { b: { c: { d: [ 1, 2 ] } } } }
)
intersectAll
Above intersect only accepts two inputs and in your question you want to compute the intersect of 2+ objects. We implement intersectAll as follows -
const None =
Symbol ()
const intersectAll = (x = None, ...xs) =>
x === None
? {}
: xs .reduce (intersect, x)
console.log
( intersectAll
( { a: 1, b: 2, c: { d: 3, e: 4 } }
, { a: 1, b: 9, c: { d: 3, e: 4 } }
, { a: 1, b: 2, c: { d: 3, e: 5 } }
)
// { a: 1, c: { d: 3 } }
, intersectAll
( { a: 1 }
, { b: 2 }
, { c: 3 }
)
// {}
, intersectAll
()
// {}
)
Verify the results in your browser –
const isObject = x =>
Object (x) === x
const isArray =
Array.isArray
const assign = (o, [ k, v ]) =>
(o [k] = v, o)
const merge = (left = {}, right = {}) =>
Object.entries (right)
.map
( ([ k, v ]) =>
isObject (v) && isObject (left [k])
? [ k, merge (left [k], v) ]
: [ k, v ]
)
.reduce (assign, left)
const intersect = (left = {}, right = {}) =>
merge
( intersect1 (left, right)
, intersect1 (right, left)
)
const intersect1 = (left = {}, right = {}) =>
Object.entries (left)
.map
( ([ k, v ]) =>
isObject (v) && isObject (right[k])
? [ k, intersect (v, right[k]) ]
: v === right[k]
? [ k, v ]
: [ k, {} ]
)
.filter
( ([ k, v ]) =>
isObject (v)
? Object.keys (v) .length > 0
: true
)
.reduce
( assign
, isArray (left) && isArray (right) ? [] : {}
)
const None =
Symbol ()
const intersectAll = (x = None, ...xs) =>
x === None
? {}
: xs .reduce (intersect, x)
console.log
( intersectAll
( { a: 1, b: 2, c: { d: 3, e: 4 } }
, { a: 1, b: 9, c: { d: 3, e: 4 } }
, { a: 1, b: 2, c: { d: 3, e: 5 } }
)
// { a: 1, c: { d: 3 } }
, intersectAll
( { a: 1 }
, { b: 2 }
, { c: 3 }
)
// {}
, intersectAll
()
// {}
)
remarks
You'll want to consider some things like –
intersect
( { a: someFunc, b: x => x * 2, c: /foo/, d: 1 }
, { a: someFunc, b: x => x * 3, c: /foo/, d: 1 }
)
// { d: 1 } (actual)
// { a: someFunc, c: /foo/, d: 1 } (expected)
We're testing for what's considered equal here in intersect1 –
const intersect1 = (left = {}, right = {}) =>
Object.entries (left)
.map
( ([ k, v ]) =>
isObject (v) && isObject (right[k])
? [ k, intersect (v, right[k]) ]
: v === right[k] // <-- equality?
? [ k, v ]
: [ k, {} ]
)
.filter
( ...
If we want to support things like checking for equality of Functions, RegExps, or other objects, this is where we would make the necessary modifications
recursive diff
In this related Q&A we compute the recursive diff of two objects

Best way to get intersection of keys of two objects?

I have two object literals like so:
var firstObject =
{
x: 0,
y: 1,
z: 2,
a: 10,
b: 20,
e: 30
}
var secondObject =
{
x: 0,
y: 1,
z: 2,
a: 10,
c: 20,
d: 30
}
I want to get the intersection of the keys these two object literals have like so:
var intersectionKeys = ['x', 'y', 'z', 'a']
I can obviously do a loop and see if a key with the same name exists in the other object, but I am wondering if this would be a good case for some functional programming and map / filter / reduce usage? I myself have not done that much functional programming, but I have a feeling, that there could exist a clean and clever solution for this problem.
A solution without indexOf.
var firstObject = { x: 0, y: 1, z: 2, a: 10, b: 20, e: 30 },
secondObject = { x: 0, y: 1, z: 2, a: 10, c: 20, d: 30 };
function intersection(o1, o2) {
return Object.keys(o1).concat(Object.keys(o2)).sort().reduce(function (r, a, i, aa) {
if (i && aa[i - 1] === a) {
r.push(a);
}
return r;
}, []);
}
document.write('<pre>' + JSON.stringify(intersection(firstObject, secondObject), 0, 4) + '</pre>');
Second attempt with O(n).
var firstObject = { x: 0, y: 1, z: 2, a: 10, b: 20, e: 30 },
secondObject = { x: 0, y: 1, z: 2, a: 10, c: 20, d: 30 };
function intersection(o1, o2) {
return Object.keys(o1).filter({}.hasOwnProperty.bind(o2));
}
document.write('<pre>' + JSON.stringify(intersection(firstObject, secondObject), 0, 4) + '</pre>');
The given answers are nice and astonishing but there could be a problem in void's answer and that is:
"What if one of property values intentionally set to undefined."
Nina's answer is good (really fantastic) but as we are in era of fun JavaScript I think mine wont be too bad:
var a = { x: undefined, y: 1, z: 2, a: 10, b: 20, e: 30 }
var b = { x: 0, y: 1, z: 2, a: 10, c: 20, d: 30 }
function intersect(o1, o2){
return Object.keys(o1).filter(k => Object.hasOwn(o2, k))
}
document.write('<pre>' + JSON.stringify(intersect(a, b)) + '</pre>');
Update
onalbi mentioned some performance issue in comments which is rational and therefore the code bellow seems to be a better way to handle the problem:
var a = { x: undefined, y: 1, z: 2, a: 10, b: 20, e: 30};
var b = { x: 0, y: 1, z: 2, a: 10, c: 20, d: 30};
function intersect(o1, o2) {
const [k1, k2] = [Object.keys(o1), Object.keys(o2)];
const [first, next] = k1.length > k2.length ? [k2, o1] : [k1, o2];
return first.filter(k => k in next);
}
document.write('<pre>' + JSON.stringify(intersect(a, b)) + '</pre>');
The procedure i will suggest is:
Get the array of keys using Object.keys() for one of the objects.
Find the intersection the array using .filter and checking if the second object contains a key matching the first array.
var firstObject = {
x: 0,
y: 1,
z: 2,
a: 10,
b: 20,
e: 30
}
var secondObject = {
x: 0,
y: 1,
z: 2,
a: 10,
c: 20,
d: 30
}
function getIntKeys(obj1, obj2){
var k1 = Object.keys(obj1);
return k1.filter(function(x){
return obj2[x] !== undefined;
});
}
alert(getIntKeys(firstObject, secondObject));
Recursive function
This is other solution, maybe help you. I used a recursive function to intercept two objects. The advantage of this solution is that you not need worry about attributes that are objects at same time.
In this case the function intercept attributes that exist in both objects and asign the value of 'objSource' like final value of attribute intercepeted.
{
function interceptObjects(objSource, objInterface) {
let newObj = {};
for (const key in objSource) {
if (objInterface.hasOwnProperty(key)) {
// in javascript an array is a object too.
if (objSource[key] instanceof Object && !Array.isArray(objSource[key]) && objInterface[key] instanceof Object && !Array.isArray(objInterface[key])) {
newObj[key] = {};
newObj[key] = interceptObjects(objSource[key], objInterface[key])
} else {
newObj[key] = objSource[key];
}
}
}
return newObj;
}
// FOR TESTING
let objSource = {
attr1: '',
attr2: 2,
attr3: [],
attr4: {
attr41: 'lol',
attr42: 12,
attr43: 15,
attr45: [1, 4],
},
attr5: [2, 3, 4],
};
let objInterface = {
attr1: null,
attr4: {
attr41: null,
attr42: 12,
attr45: [1],
},
attr5: [],
attr6: null,
};
console.log(this.interceptObjects(objSource, objInterface));
}
Here is a simple entry, very functional, handles any number of objects, and returns the values of the matching keys from the first object passed.
This behavior is similar to that of array_intersect_key() in PHP in case anyone is searching for that.
function intersectKeys(first, ...rest) {
const restKeys = rest.map(o => Object.keys(o));
return Object.fromEntries(Object.entries(first).filter(entry => restKeys.every(rk => rk.includes(entry[0]))));
}
Expanded here for better explanation and commenting
function intersectKeys(first, ...rest) {
// extract the keys of the other objects first so that won't be done again for each check
const restKeys = rest.map(o => Object.keys(o));
// In my version I am returning the first objects values under the intersect keys
return Object.fromEntries(
// extract [key, value] sets for each key and filter them, Object.fromEntries() reverses this back into an object of the remaining fields after the filter
Object.entries(first).filter(
// make sure each of the other object key sets includes the current key, or filter it out
entry => restKeys.every(
rk => rk.includes(entry[0])
)
)
);
// to get JUST the keys as OP requested the second line would simplify down to this
return Object.keys(first).filter(key => restKeys.every(rk => rk.includes(key)));
}
It's important to note that this solution only works on string keys, Symbol keys will be ignored and the final object will not contain any. Though a similar function could be written to compare Symbol intersect as well.
I know this is an old post, however, I want to share a solution I wrote today that I believe is efficient and clean.
function intersectingKeys(...objects) {
return objects
.map((object) => Object.keys(object))
.sort((a, b) => a.length - b.length)
.reduce((a, b) => a.filter((key) => b.includes(key)));
}
This function can take in n number of objects, and find the intersecting keys.
This is how it works.
Map the objects, creating an array of key arrays.
Sort the array by length, this puts the smallest key arrays first.
Finally, reduce our key arrays, by filtering each list of keys against the next list.
I think the clever part of this algorithm is the pre sorting of the key arrays. By starting with the smallest list of keys, we have less work to do comparing keys.
Here is the usuage:
var firstObject = {
x: 0,
y: 1,
z: 2,
a: 10,
b: 20,
e: 30,
};
var secondObject = {
x: 0,
y: 1,
z: 2,
a: 10,
c: 20,
d: 30,
};
intersectingKeys(firstObject, secondObject);
// [ 'x', 'y', 'z', 'a' ]

Categories

Resources