From 658d1fd454fe095e6c7f2be22ccf4823fe0ea6ef Mon Sep 17 00:00:00 2001 From: Rebecca Stevens Date: Thu, 24 Feb 2022 03:16:54 +1300 Subject: [PATCH] feat: allow for default merging via a special return value --- docs/deepmergeCustom.md | 25 +++++++++++++++--- src/deepmerge.ts | 47 ++++++++++++++++++++++------------ src/types/options.ts | 3 +++ tests/deepmerge-custom.test.ts | 35 +++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 20 deletions(-) diff --git a/docs/deepmergeCustom.md b/docs/deepmergeCustom.md index 5c1e4240..6133c6b3 100644 --- a/docs/deepmergeCustom.md +++ b/docs/deepmergeCustom.md @@ -202,11 +202,10 @@ declare module "../src/types" { } ``` -## Implicit Default Merging +## Default Merging -If you do not want to have to explicitly perform default merging in your custom merge functions; -you can set the option `enableImplicitDefaultMerging` to `true`. Once set, if any of your custom -merge functions return `undefined`, then the default merging functions will automatically be called. +If you do not want to have to explicitly call the default merging function in your custom merge function; +you can just return `utils.actions.defaultMerge`. This will automatically apply the default merging strategy. For example, the following `customizedDeepmerge` functions are equivalent: @@ -221,6 +220,24 @@ const customizedDeepmerge = deepmergeCustom({ }); ``` +```ts +const customizedDeepmerge = deepmergeCustom({ + mergeOthers: (value, utils) => { + if (someCondition) { + return someCustomValue; + } + return utils.actions.defaultMerge; + }, +}); +``` + +### Implicit + +You can also set the option `enableImplicitDefaultMerging` to `true` to make it so that if any of your +custom merge functions return `undefined`, then the default merging strategy will automatically be applied. + +For example, the following `customizedDeepmerge` function is equivalent to the two above: + ```ts const customizedDeepmerge = deepmergeCustom({ enableImplicitDefaultMerging: true, // enable implicit default merging diff --git a/src/deepmerge.ts b/src/deepmerge.ts index c315294f..49775b10 100644 --- a/src/deepmerge.ts +++ b/src/deepmerge.ts @@ -27,6 +27,13 @@ const defaultMergeFunctions = { mergeOthers: leaf, } as const; +/** + * Special values that tell deepmerge-ts to perform a certain action. + */ +const actions = { + defaultMerge: Symbol("deepmerge-ts: default merge"), +} as const; + /** * The default function to update meta data. */ @@ -163,6 +170,7 @@ function getUtils( >["metaDataUpdater"], deepmerge: customizedDeepmerge, useImplicitDefaultMerging: options.enableImplicitDefaultMerging ?? false, + actions, }; } @@ -263,10 +271,11 @@ function mergeRecords< const result = utils.mergeFunctions.mergeRecords(values, utils, meta); if ( - utils.useImplicitDefaultMerging && - result === undefined && - utils.mergeFunctions.mergeRecords !== - utils.defaultMergeFunctions.mergeRecords + result === actions.defaultMerge || + (utils.useImplicitDefaultMerging && + result === undefined && + utils.mergeFunctions.mergeRecords !== + utils.defaultMergeFunctions.mergeRecords) ) { return utils.defaultMergeFunctions.mergeRecords< ReadonlyArray>>, @@ -297,9 +306,11 @@ function mergeArrays< const result = utils.mergeFunctions.mergeArrays(values, utils, meta); if ( - utils.useImplicitDefaultMerging && - result === undefined && - utils.mergeFunctions.mergeArrays !== utils.defaultMergeFunctions.mergeArrays + result === actions.defaultMerge || + (utils.useImplicitDefaultMerging && + result === undefined && + utils.mergeFunctions.mergeArrays !== + utils.defaultMergeFunctions.mergeArrays) ) { return utils.defaultMergeFunctions.mergeArrays(values); } @@ -323,9 +334,10 @@ function mergeSets< const result = utils.mergeFunctions.mergeSets(values, utils, meta); if ( - utils.useImplicitDefaultMerging && - result === undefined && - utils.mergeFunctions.mergeSets !== utils.defaultMergeFunctions.mergeSets + result === actions.defaultMerge || + (utils.useImplicitDefaultMerging && + result === undefined && + utils.mergeFunctions.mergeSets !== utils.defaultMergeFunctions.mergeSets) ) { return utils.defaultMergeFunctions.mergeSets(values); } @@ -349,9 +361,10 @@ function mergeMaps< const result = utils.mergeFunctions.mergeMaps(values, utils, meta); if ( - utils.useImplicitDefaultMerging && - result === undefined && - utils.mergeFunctions.mergeMaps !== utils.defaultMergeFunctions.mergeMaps + result === actions.defaultMerge || + (utils.useImplicitDefaultMerging && + result === undefined && + utils.mergeFunctions.mergeMaps !== utils.defaultMergeFunctions.mergeMaps) ) { return utils.defaultMergeFunctions.mergeMaps(values); } @@ -371,9 +384,11 @@ function mergeOthers< const result = utils.mergeFunctions.mergeOthers(values, utils, meta); if ( - utils.useImplicitDefaultMerging && - result === undefined && - utils.mergeFunctions.mergeOthers !== utils.defaultMergeFunctions.mergeOthers + result === actions.defaultMerge || + (utils.useImplicitDefaultMerging && + result === undefined && + utils.mergeFunctions.mergeOthers !== + utils.defaultMergeFunctions.mergeOthers) ) { return utils.defaultMergeFunctions.mergeOthers(values); } diff --git a/src/types/options.ts b/src/types/options.ts index e4375c54..459e4805 100644 --- a/src/types/options.ts +++ b/src/types/options.ts @@ -93,4 +93,7 @@ export type DeepMergeMergeFunctionUtils< metaDataUpdater: MetaDataUpdater; deepmerge: >(...values: Ts) => unknown; useImplicitDefaultMerging: boolean; + actions: Readonly<{ + defaultMerge: symbol; + }>; }>; diff --git a/tests/deepmerge-custom.test.ts b/tests/deepmerge-custom.test.ts index a252428f..4558499e 100644 --- a/tests/deepmerge-custom.test.ts +++ b/tests/deepmerge-custom.test.ts @@ -629,3 +629,38 @@ test("implicit default merging", (t) => { t.deepEqual(merged, expected); }); + +test("default merging using shortcut", (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({ + mergeRecords: (value, utils) => utils.actions.defaultMerge, + mergeArrays: (value, utils) => utils.actions.defaultMerge, + mergeSets: (value, utils) => utils.actions.defaultMerge, + mergeMaps: (value, utils) => utils.actions.defaultMerge, + mergeOthers: (value, utils) => utils.actions.defaultMerge, + }); + + const merged = customizedDeepmerge(x, y); + + t.deepEqual(merged, expected); +});