Skip to content

Commit

Permalink
feat: allow for alternative ways to apply default merging
Browse files Browse the repository at this point in the history
  • Loading branch information
RebeccaStevens committed Feb 24, 2022
1 parent e82b5f0 commit b8dd88a
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 30 deletions.
45 changes: 39 additions & 6 deletions docs/deepmergeCustom.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,41 @@ 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, skipping 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.defaultMerge;
},
});
```

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.use.defaultMerging`. This will automatically apply the default merging strategy.
you can just return `utils.actions.defaultMerge`. This will automatically apply the default merging strategy.

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

Expand All @@ -226,21 +257,23 @@ const customizedDeepmerge = deepmergeCustom({
if (someCondition) {
return someCustomValue;
}
return utils.use.defaultMerging;
return utils.actions.defaultMerge;
},
});
```

### 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 `allowImplicitDefaultMerging` to `true` to make it so that if any of your
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({
allowImplicitDefaultMerging: true, // enable implicit default merging
enableImplicitDefaultMerging: true, // enable implicit default merging
mergeOthers: (value, utils) => {
if (someCondition) {
return someCustomValue;
Expand Down
31 changes: 17 additions & 14 deletions src/deepmerge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,11 @@ const defaultMergeFunctions = {
mergeOthers: leaf,
} as const;

const shortcutActions = {
defaultMerging: Symbol("deepmerge-ts: default merging"),
/**
* Special values that tell deepmerge-ts to perform a certain action.
*/
const actions = {
defaultMerge: Symbol("deepmerge-ts: default merge"),
} as const;

/**
Expand Down Expand Up @@ -166,8 +169,8 @@ function getUtils<M, MM extends DeepMergeBuiltInMetaData>(
MM
>["metaDataUpdater"],
deepmerge: customizedDeepmerge,
allowImplicitDefaultMerging: options.allowImplicitDefaultMerging ?? false,
use: shortcutActions,
useImplicitDefaultMerging: options.enableImplicitDefaultMerging ?? false,
actions,
};
}

Expand Down Expand Up @@ -268,8 +271,8 @@ function mergeRecords<
const result = utils.mergeFunctions.mergeRecords(values, utils, meta);

if (
result === shortcutActions.defaultMerging ||
(utils.allowImplicitDefaultMerging &&
result === actions.defaultMerge ||
(utils.useImplicitDefaultMerging &&
result === undefined &&
utils.mergeFunctions.mergeRecords !==
utils.defaultMergeFunctions.mergeRecords)
Expand Down Expand Up @@ -303,8 +306,8 @@ function mergeArrays<
const result = utils.mergeFunctions.mergeArrays(values, utils, meta);

if (
result === shortcutActions.defaultMerging ||
(utils.allowImplicitDefaultMerging &&
result === actions.defaultMerge ||
(utils.useImplicitDefaultMerging &&
result === undefined &&
utils.mergeFunctions.mergeArrays !==
utils.defaultMergeFunctions.mergeArrays)
Expand All @@ -331,8 +334,8 @@ function mergeSets<
const result = utils.mergeFunctions.mergeSets(values, utils, meta);

if (
result === shortcutActions.defaultMerging ||
(utils.allowImplicitDefaultMerging &&
result === actions.defaultMerge ||
(utils.useImplicitDefaultMerging &&
result === undefined &&
utils.mergeFunctions.mergeSets !== utils.defaultMergeFunctions.mergeSets)
) {
Expand All @@ -358,8 +361,8 @@ function mergeMaps<
const result = utils.mergeFunctions.mergeMaps(values, utils, meta);

if (
result === shortcutActions.defaultMerging ||
(utils.allowImplicitDefaultMerging &&
result === actions.defaultMerge ||
(utils.useImplicitDefaultMerging &&
result === undefined &&
utils.mergeFunctions.mergeMaps !== utils.defaultMergeFunctions.mergeMaps)
) {
Expand All @@ -381,8 +384,8 @@ function mergeOthers<
const result = utils.mergeFunctions.mergeOthers(values, utils, meta);

if (
result === shortcutActions.defaultMerging ||
(utils.allowImplicitDefaultMerging &&
result === actions.defaultMerge ||
(utils.useImplicitDefaultMerging &&
result === undefined &&
utils.mergeFunctions.mergeOthers !==
utils.defaultMergeFunctions.mergeOthers)
Expand Down
9 changes: 5 additions & 4 deletions src/types/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +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;
enableImplicitDefaultMerging: boolean;
}>;

/**
Expand Down Expand Up @@ -92,8 +92,9 @@ export type DeepMergeMergeFunctionUtils<
defaultMergeFunctions: DeepMergeMergeFunctionsDefaults;
metaDataUpdater: MetaDataUpdater<M, MM>;
deepmerge: <Ts extends ReadonlyArray<unknown>>(...values: Ts) => unknown;
allowImplicitDefaultMerging: boolean;
use: Readonly<{
defaultMerging: symbol;
useImplicitDefaultMerging: boolean;
actions: Readonly<{
defaultMerge: symbol;
skip: symbol;
}>;
}>;
37 changes: 31 additions & 6 deletions tests/deepmerge-custom.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,7 @@ test("implicit default merging", (t) => {
};

const customizedDeepmerge = deepmergeCustom({
allowImplicitDefaultMerging: true,
enableImplicitDefaultMerging: true,
mergeRecords: () => undefined,
mergeArrays: () => undefined,
mergeSets: () => undefined,
Expand Down Expand Up @@ -653,11 +653,36 @@ test("default merging using shortcut", (t) => {
};

const customizedDeepmerge = deepmergeCustom({
mergeRecords: (value, utils) => utils.use.defaultMerging,
mergeArrays: (value, utils) => utils.use.defaultMerging,
mergeSets: (value, utils) => utils.use.defaultMerging,
mergeMaps: (value, utils) => utils.use.defaultMerging,
mergeOthers: (value, utils) => utils.use.defaultMerging,
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);
});

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.defaultMerge,
});

const merged = customizedDeepmerge(x, y);
Expand Down

0 comments on commit b8dd88a

Please sign in to comment.