Skip to content

Commit

Permalink
feat: allow for implicit default merging
Browse files Browse the repository at this point in the history
  • Loading branch information
RebeccaStevens committed Feb 23, 2022
1 parent 9a881d3 commit 67ed0db
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 19 deletions.
31 changes: 31 additions & 0 deletions docs/deepmergeCustom.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,37 @@ declare module "../src/types" {
}
```

## Implicit Default Merging

If you do not want to have to explicitly perform default merging in your custom merge functions;
you can set the option `allowImplicitDefaultMerging` to `true`. Once set, if any of your custom
merge functions return `undefined`, then the default merging functions will automatically be called.

For example, the following `customizedDeepmerge` functions are equivalent:

```ts
const customizedDeepmerge = deepmergeCustom({
mergeOthers: (value, utils) => {
if (someCondition) {
return someCustomValue;
}
return utils.defaultMergeFunctions.mergeOthers(values);
},
});
```

```ts
const customizedDeepmerge = deepmergeCustom({
allowImplicitDefaultMerging: true, // enable implicit default merging
mergeOthers: (value, utils) => {
if (someCondition) {
return someCustomValue;
}
// implicitly return undefined
},
});
```

## API

[See deepmerge custom API](./API.md#deepmergecustomoptions).
174 changes: 155 additions & 19 deletions src/deepmerge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ function getUtils<M, MM extends DeepMergeBuiltInMetaData>(
MM
>["metaDataUpdater"],
deepmerge: customizedDeepmerge,
allowImplicitDefaultMerging: options.allowImplicitDefaultMerging ?? false,
};
}

Expand All @@ -181,11 +182,11 @@ function mergeUnknowns<
return undefined as DeepMergeHKT<Ts, MF, M>;
}
if (values.length === 1) {
return utils.mergeFunctions.mergeOthers(
values,
utils,
meta
) as DeepMergeHKT<Ts, MF, M>;
return mergeOthers<U, M, MM>(values, utils, meta) as DeepMergeHKT<
Ts,
MF,
M
>;
}

const type = getObjectType(values[0]);
Expand All @@ -198,50 +199,185 @@ function mergeUnknowns<
continue;
}

return utils.mergeFunctions.mergeOthers(
values,
utils,
meta
) as DeepMergeHKT<Ts, MF, M>;
return mergeOthers<U, M, MM>(values, utils, meta) as DeepMergeHKT<
Ts,
MF,
M
>;
}
}

switch (type) {
case ObjectType.RECORD:
return utils.mergeFunctions.mergeRecords(
return mergeRecords<U, MF, M, MM>(
values as ReadonlyArray<Readonly<Record<PropertyKey, unknown>>>,
utils,
meta
) as DeepMergeHKT<Ts, MF, M>;

case ObjectType.ARRAY:
return utils.mergeFunctions.mergeArrays(
return mergeArrays<U, M, MM>(
values as ReadonlyArray<Readonly<ReadonlyArray<unknown>>>,
utils,
meta
) as DeepMergeHKT<Ts, MF, M>;

case ObjectType.SET:
return utils.mergeFunctions.mergeSets(
return mergeSets<U, M, MM>(
values as ReadonlyArray<Readonly<ReadonlySet<unknown>>>,
utils,
meta
) as DeepMergeHKT<Ts, MF, M>;

case ObjectType.MAP:
return utils.mergeFunctions.mergeMaps(
return mergeMaps<U, M, MM>(
values as ReadonlyArray<Readonly<ReadonlyMap<unknown, unknown>>>,
utils,
meta
) as DeepMergeHKT<Ts, MF, M>;

default:
return utils.mergeFunctions.mergeOthers(
values,
utils,
meta
) as DeepMergeHKT<Ts, MF, M>;
return mergeOthers<U, M, MM>(values, utils, meta) as DeepMergeHKT<
Ts,
MF,
M
>;
}
}

/**
* Merge records.
*
* @param values - The records.
*/
function mergeRecords<
U extends DeepMergeMergeFunctionUtils<M, MM>,
MF extends DeepMergeMergeFunctionsURIs,
M,
MM extends DeepMergeBuiltInMetaData
>(
values: ReadonlyArray<Readonly<Record<PropertyKey, unknown>>>,
utils: U,
meta: M | undefined
) {
const result = utils.mergeFunctions.mergeRecords(values, utils, meta);

if (
utils.allowImplicitDefaultMerging &&
result === undefined &&
utils.mergeFunctions.mergeRecords !==
utils.defaultMergeFunctions.mergeRecords
) {
return utils.defaultMergeFunctions.mergeRecords<
ReadonlyArray<Readonly<Record<PropertyKey, unknown>>>,
U,
MF,
M,
MM
>(values, utils, meta);
}

return result;
}

/**
* Merge arrays.
*
* @param values - The arrays.
*/
function mergeArrays<
U extends DeepMergeMergeFunctionUtils<M, MM>,
M,
MM extends DeepMergeBuiltInMetaData
>(
values: ReadonlyArray<Readonly<ReadonlyArray<unknown>>>,
utils: U,
meta: M | undefined
) {
const result = utils.mergeFunctions.mergeArrays(values, utils, meta);

if (
utils.allowImplicitDefaultMerging &&
result === undefined &&
utils.mergeFunctions.mergeArrays !== utils.defaultMergeFunctions.mergeArrays
) {
return utils.defaultMergeFunctions.mergeArrays(values);
}
return result;
}

/**
* Merge sets.
*
* @param values - The sets.
*/
function mergeSets<
U extends DeepMergeMergeFunctionUtils<M, MM>,
M,
MM extends DeepMergeBuiltInMetaData
>(
values: ReadonlyArray<Readonly<ReadonlySet<unknown>>>,
utils: U,
meta: M | undefined
) {
const result = utils.mergeFunctions.mergeSets(values, utils, meta);

if (
utils.allowImplicitDefaultMerging &&
result === undefined &&
utils.mergeFunctions.mergeSets !== utils.defaultMergeFunctions.mergeSets
) {
return utils.defaultMergeFunctions.mergeSets(values);
}
return result;
}

/**
* Merge maps.
*
* @param values - The maps.
*/
function mergeMaps<
U extends DeepMergeMergeFunctionUtils<M, MM>,
M,
MM extends DeepMergeBuiltInMetaData
>(
values: ReadonlyArray<Readonly<ReadonlyMap<unknown, unknown>>>,
utils: U,
meta: M | undefined
) {
const result = utils.mergeFunctions.mergeMaps(values, utils, meta);

if (
utils.allowImplicitDefaultMerging &&
result === undefined &&
utils.mergeFunctions.mergeMaps !== utils.defaultMergeFunctions.mergeMaps
) {
return utils.defaultMergeFunctions.mergeMaps(values);
}
return result;
}

/**
* Merge other things.
*
* @param values - The other things.
*/
function mergeOthers<
U extends DeepMergeMergeFunctionUtils<M, MM>,
M,
MM extends DeepMergeBuiltInMetaData
>(values: ReadonlyArray<unknown>, utils: U, meta: M | undefined) {
const result = utils.mergeFunctions.mergeOthers(values, utils, meta);

if (
utils.allowImplicitDefaultMerging &&
result === undefined &&
utils.mergeFunctions.mergeOthers !== utils.defaultMergeFunctions.mergeOthers
) {
return utils.defaultMergeFunctions.mergeOthers(values);
}
return result;
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/types/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type DeepMergeOptionsFull<M, MM extends DeepMergeBuiltInMetaData> = Readonly<{
mergeSets: DeepMergeMergeFunctions<M, MM>["mergeSets"] | false;
mergeOthers: DeepMergeMergeFunctions<M, MM>["mergeOthers"];
metaDataUpdater: MetaDataUpdater<M, MM>;
allowImplicitDefaultMerging: boolean;
}>;

/**
Expand Down Expand Up @@ -91,4 +92,5 @@ export type DeepMergeMergeFunctionUtils<
defaultMergeFunctions: DeepMergeMergeFunctionsDefaults;
metaDataUpdater: MetaDataUpdater<M, MM>;
deepmerge: <Ts extends ReadonlyArray<unknown>>(...values: Ts) => unknown;
allowImplicitDefaultMerging: boolean;
}>;
36 changes: 36 additions & 0 deletions tests/deepmerge-custom.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -593,3 +593,39 @@ test("custom merge that clones", (t) => {
t.not(merged.bar, y.bar);
t.not(merged.baz, y.baz);
});

test("implicit default merging", (t) => {
const x = {
foo: 1,
bar: { baz: [2], qux: new Set([1]), quux: new Map([[1, 2]]) },
};
const y = {
foo: 3,
bar: { baz: [4], qux: new Set([2]), quux: new Map([[2, 3]]) },
};

const expected = {
foo: 3,
bar: {
baz: [2, 4],
qux: new Set([1, 2]),
quux: new Map([
[1, 2],
[2, 3],
]),
},
};

const customizedDeepmerge = deepmergeCustom({
allowImplicitDefaultMerging: true,
mergeRecords: () => undefined,
mergeArrays: () => undefined,
mergeSets: () => undefined,
mergeMaps: () => undefined,
mergeOthers: () => undefined,
});

const merged = customizedDeepmerge(x, y);

t.deepEqual(merged, expected);
});

0 comments on commit 67ed0db

Please sign in to comment.