From bf151f5b41870e2cb4a09f27d563c4b1e7d37d5e Mon Sep 17 00:00:00 2001 From: Rebecca Stevens Date: Thu, 24 Feb 2022 21:15:58 +1300 Subject: [PATCH] feat: allow for skipping properties completely via a special return value --- docs/deepmergeCustom.md | 37 ++++++++++++++++++++++++++++++++-- src/deepmerge.ts | 9 ++++++++- src/types/options.ts | 1 + tests/deepmerge-custom.test.ts | 25 +++++++++++++++++++++++ 4 files changed, 69 insertions(+), 3 deletions(-) diff --git a/docs/deepmergeCustom.md b/docs/deepmergeCustom.md index 6133c6b33..90b83b28f 100644 --- a/docs/deepmergeCustom.md +++ b/docs/deepmergeCustom.md @@ -202,7 +202,38 @@ declare module "../src/types" { } ``` -## Default Merging +## Special Actions + +We provide a couple of special actions under `utils.actions` that you can use to simplify your custom merge functions. + +### Skipping a Property (`utils.actions.skip`) + +If you want to skip a property from being included in the result of a merge based on its value or metadata, you can easily do so with this action. + +For example, skiping all properties under the key `"skipme"` of type `Date`: + +```ts +const customizedDeepmerge = deepmergeCustom({ + mergeOthers: (value, utils, meta) => { + if (meta?.key === "skipme") { + const nonDateValues = values.filter((value) => !(value instanceof Date)); + if (nonDateValues.length === 0) { + return utils.actions.skip; // Completely skip this property + } + + // Don't skip the property completely if a non-Date value was found. + return utils.defaultMergeFunctions.mergeOthers(nonDateValues); + } + + // Perform the default merging (see below). + return utils.actions.defaultMerging; + }, +}); +``` + +To do this without any special actions would require using a custom `mergeRecords` which would be a bit more complicated. + +### Default Merging (`utils.actions.defaultMerge`) 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. @@ -231,7 +262,9 @@ const customizedDeepmerge = deepmergeCustom({ }); ``` -### Implicit +Note: When using this action, you cannot change the values upon which the default merging will apply. + +#### Implicit Default Merging 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. diff --git a/src/deepmerge.ts b/src/deepmerge.ts index 49775b105..777929f43 100644 --- a/src/deepmerge.ts +++ b/src/deepmerge.ts @@ -32,6 +32,7 @@ const defaultMergeFunctions = { */ const actions = { defaultMerge: Symbol("deepmerge-ts: default merge"), + skip: Symbol("deepmerge-ts: skip"), } as const; /** @@ -427,11 +428,17 @@ function defaultMergeRecords< parents: values, } as unknown as MM); - result[key] = mergeUnknowns, U, MF, M, MM>( + const propertyResult = mergeUnknowns, U, MF, M, MM>( propValues, utils, updatedMeta ); + + if (propertyResult === actions.skip) { + continue; + } + + result[key] = propertyResult; } /* eslint-enable functional/no-loop-statement, functional/no-conditional-statement */ diff --git a/src/types/options.ts b/src/types/options.ts index 459e4805c..9cb8e4472 100644 --- a/src/types/options.ts +++ b/src/types/options.ts @@ -95,5 +95,6 @@ export type DeepMergeMergeFunctionUtils< useImplicitDefaultMerging: boolean; actions: Readonly<{ defaultMerge: symbol; + skip: symbol; }>; }>; diff --git a/tests/deepmerge-custom.test.ts b/tests/deepmerge-custom.test.ts index 4558499ed..112add7ae 100644 --- a/tests/deepmerge-custom.test.ts +++ b/tests/deepmerge-custom.test.ts @@ -664,3 +664,28 @@ test("default merging using shortcut", (t) => { t.deepEqual(merged, expected); }); + +test("skip property", (t) => { + const x = { + foo: { bar: 1, baz: 2, qux: ["a"] }, + bar: [1, 2, 3], + }; + const y = { + foo: { bar: 3, baz: 4, qux: ["b"] }, + bar: [4, 5, 6], + }; + + const expected = { + foo: { baz: 4, qux: ["a", "b"] }, + bar: [1, 2, 3, 4, 5, 6], + }; + + const customizedDeepmerge = deepmergeCustom({ + mergeOthers: (value, utils, meta) => + meta?.key === "bar" ? utils.actions.skip : utils.actions.defaultMerging, + }); + + const merged = customizedDeepmerge(x, y); + + t.deepEqual(merged, expected); +});