TehShrike / deepmerge

A library for deep (recursive) merging of Javascript objects
MIT License
2.75k stars 216 forks source link

Map and Set merging support #204

Open RebeccaStevens opened 3 years ago

RebeccaStevens commented 3 years ago

I'm not sure if this is in the scope of this library or not but I think it would be a good to have support for merging Sets and Maps out of the box.

I needed support for this functionality for a project I'm working on and so I wrote my own wrapper for this lib to add support. I thought I should drop the code snippet here in case anyone else needed this functionality.

import deepMergeLib from 'deepmerge';
import isPlainObject from 'is-plain-object';

export function mergeMaps<K, V>(
  maps: ReadonlyArray<ReadonlyMap<K, V>>,
  deep = false
): Map<K, V> {
  return maps.reduce<Map<K, V>>((mutableCarry, current) => {
    for (const [key, value] of current.entries()) {
      const currentValue = mutableCarry.get(key);
      mutableCarry.set(
        key,
        deep && typeof value === 'object' && typeof currentValue === 'object'
          ? ((deepMerge([
              (currentValue as unknown) as object,
              (value as unknown) as object,
            ]) as unknown) as V)
          : value
      );
    }
    return mutableCarry;
  }, new Map<K, V>());
}

export function mergeSets<V>(sets: ReadonlyArray<ReadonlySet<V>>): Set<V> {
  return sets.reduce<Set<V>>((mutableCarry, current) => {
    for (const value of current.values()) {
      mutableCarry.add(value);
    }
    return mutableCarry;
  }, new Set<V>());
}

export function deepMerge<T extends object>(
  values: ReadonlyArray<T>,
  options = { mergeMaps: true, mergeSets: true }
): T {
  const [mergeAsMaps, mergeAsSets] =
    (options.mergeMaps || options.mergeSets) && values.length > 0
      ? values.reduce(
          (carry, value) => [
            carry[0] && value instanceof Map,
            carry[1] && value instanceof Set,
          ],
          [options.mergeMaps, options.mergeSets]
        )
      : [false, false];

  if (mergeAsSets) {
    return mergeSets(values as any) as T;
  }
  if (mergeAsMaps) {
    return mergeMaps(values as any, true) as T;
  }

  return deepMergeLib.all(values as Array<T>, {
    customMerge: () => (a: T, b: T) => deepMerge<T>([a, b], options),
    isMergeableObject: (value: object) =>
      (options.mergeSets && value instanceof Set) ||
      (options.mergeMaps && value instanceof Map) ||
      isPlainObject(value),
    clone: false,
  }) as T;
}
RebeccaStevens commented 2 years ago

I've made my own package now that has builtin support for set and map merging as well as some other benefits. deepmerge-ts