Reputation: 3709
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?
Upvotes: 8
Views: 11388
Reputation: 135416
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?
Upvotes: 1
Reputation: 2796
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>
Upvotes: 1
Reputation: 50807
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.
(Two years later.) This was eventually added, in the form of several functions: mergeDeepLeft
, mergeDeepRight
, mergeDeepWith
, and mergeDeepWithKey
.
Upvotes: 10
Reputation: 685
Ramda now has several merge functions: mergeDeepLeft, mergeDeepRight, mergeDeepWith, mergeDeepWithKey.
Upvotes: 3
Reputation: 6516
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"}
Upvotes: 11