Himmel
Himmel

Reputation: 3709

Recursive "merge" or "extend" with Ramda?

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

Answers (5)

Mulan
Mulan

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

cstuncsik
cstuncsik

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

Scott Sauyet
Scott Sauyet

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.

Update

(Two years later.) This was eventually added, in the form of several functions: mergeDeepLeft, mergeDeepRight, mergeDeepWith, and mergeDeepWithKey.

Upvotes: 10

Microcipcip
Microcipcip

Reputation: 685

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

Upvotes: 3

Scott Christopher
Scott Christopher

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

Related Questions