Skip to content

Commit

Permalink
feat: introduce arrayPriority option (#328)
Browse files Browse the repository at this point in the history
* feat: introduce option arrayPriority

* fix: toggle array-priority if difer from priority option

* fix: reading correct otpion value

* test: enhance deeply merging items with different priorities

* fix: optimized inPlace merging
  • Loading branch information
tada5hi committed Mar 29, 2024
1 parent 27620de commit 7d287bf
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 17 deletions.
3 changes: 2 additions & 1 deletion README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@ const output = merge(...sources);
The following merge options are set by default:
- **array**: `true` Merge object array properties.
- **arrayDistinct**: `false` Remove duplicates, when merging array elements.
- **arrayPriority**: `left` (options.priority) The source aka leftmost array has by **default** the highest priority.
- **clone**: `false` Deep clone input sources.
- **inPlace**: `false` Merge sources in place.
- **priority**: `left` The source aka leftmost array/object has by **default** the highest priority.
- **priority**: `left` The source aka leftmost object has by **default** the highest priority.

The merge behaviour can be changed by creating a custom [merger](#merger).

Expand Down
47 changes: 36 additions & 11 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
distinctArray,
hasOwnProperty,
isObject,
isSafeKey,
isSafeKey, togglePriority,
} from './utils';

function baseMerger<B extends MergerSource[]>(
Expand All @@ -22,7 +22,18 @@ function baseMerger<B extends MergerSource[]>(
) : MergerResult<B> {
let target : MergerSourceUnwrap<B>;
let source : MergerSourceUnwrap<B> | undefined;
if (context.options.priority === PriorityName.RIGHT) {

let { priority } = context.options;
if (sources.length >= 2) {
if (
Array.isArray(sources.at(0)) &&
Array.isArray(sources.at(-1))
) {
priority = context.options.arrayPriority;
}
}

if (priority === PriorityName.RIGHT) {
target = sources.pop() as MergerSourceUnwrap<B>;
source = sources.pop() as MergerSourceUnwrap<B>;
} else {
Expand All @@ -47,7 +58,7 @@ function baseMerger<B extends MergerSource[]>(
) {
target.push(...source as MergerSource[]);

if (context.options.priority === PriorityName.RIGHT) {
if (context.options.arrayPriority === PriorityName.RIGHT) {
return baseMerger(
context,
...sources,
Expand Down Expand Up @@ -124,7 +135,11 @@ function baseMerger<B extends MergerSource[]>(
Array.isArray(target[key]) &&
Array.isArray(source[key])
) {
switch (context.options.priority) {
const arrayPriority = context.options.priority !== context.options.arrayPriority ?
togglePriority(context.options.arrayPriority) :
context.options.arrayPriority;

switch (arrayPriority) {
case PriorityName.LEFT:
Object.assign(target, {
[key]: baseMerger(context, target[key] as MergerSource, source[key] as MergerSource),
Expand Down Expand Up @@ -174,14 +189,24 @@ export function createMerger(input?: OptionsInput) : Merger {
}

if (!options.inPlace) {
if (options.priority === PriorityName.LEFT) {
if (Array.isArray(sources[0])) {
sources.unshift([]);
} else {
sources.unshift({});
}
} else if (Array.isArray(sources[0])) {
if (
Array.isArray(sources.at(0)) &&
options.arrayPriority === PriorityName.LEFT
) {
sources.unshift([]);
return baseMerger(ctx, ...sources);
}

if (
Array.isArray(sources.at(-1)) &&
options.arrayPriority === PriorityName.RIGHT
) {
sources.push([]);
return baseMerger(ctx, ...sources);
}

if (options.priority === PriorityName.LEFT) {
sources.unshift({});
} else {
sources.push({});
}
Expand Down
8 changes: 8 additions & 0 deletions src/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ export type Options = {
* default: false
*/
arrayDistinct: boolean,
/**
* Merge sources from left-to-right or right-to-left.
* From v2 upwards default to left independent of the option priority.
*
* default: left (aka. options.priority)
*/
arrayPriority: `${PriorityName}`,
/**
* Strategy to merge different object keys.
*
Expand All @@ -38,6 +45,7 @@ export type Options = {
clone?: boolean,
/**
* Merge sources from left-to-right or right-to-left.
* From v2 upwards default to right.
*
* default: left
*/
Expand Down
11 changes: 8 additions & 3 deletions src/utils/options.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import { PriorityName } from '../constants';
import type { Options, OptionsInput } from '../type';

export function buildOptions(options?: OptionsInput) : Options {
options = options || {};

export function buildOptions(options: OptionsInput = {}) : Options {
options.array = options.array ?? true;
options.arrayDistinct = options.arrayDistinct ?? false;
options.clone = options.clone ?? false;
options.inPlace = options.inPlace ?? false;
options.priority = options.priority || PriorityName.LEFT;
options.arrayPriority = options.arrayPriority || options.priority;

return options as Options;
}

export function togglePriority(priority: `${PriorityName}`) {
return priority === PriorityName.LEFT ?
`${PriorityName.RIGHT}` :
`${PriorityName.LEFT}`;
}
65 changes: 63 additions & 2 deletions test/unit/module.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,12 +239,73 @@ describe('src/module/*.ts', () => {
});

it('should merge arrays with right priority', () => {
const merger = createMerger({ priority: 'right' });
expect(merger([4, 5, 6], [1, 2, 3, 4])).toEqual([1, 2, 3, 4, 4, 5, 6]);
const merger = createMerger({ arrayPriority: 'right' });
expect(merger([4, 5, 6], [1, 2, 3])).toEqual([1, 2, 3, 4, 5, 6]);

expect(merger({ foo: [4, 5, 6] }, { foo: [1, 2, 3, 4] })).toEqual({ foo: [1, 2, 3, 4, 4, 5, 6] });
});

it('should merge with different priorities for arrays and objects', () => {
const merger = createMerger({
arrayPriority: 'left',
priority: 'right',
});

expect(merger({ foo: [1, 2, 3], bar: 'baz' }, { foo: [4, 5, 6], bar: 'boz' })).toEqual({
foo: [1, 2, 3, 4, 5, 6],
bar: 'boz',
});

expect(merger(
{
foo: [1, 2, 3],
bar: 'baz',
biz: 'bar',
boz: 'baz',
},
{
foo: [4, 5, 6],
bar: 'biz',
biz: 'boz',
},
{
foo: [7, 8, 9],
bar: 'boz',
},
)).toEqual({
foo: [1, 2, 3, 4, 5, 6, 7, 8, 9],
bar: 'boz',
biz: 'boz',
boz: 'baz',
});

expect(merger(
{
foo: {
bar: [1, 2, 3],
},
bar: 'baz',
},
{
foo: {
bar: [4, 5, 6],
},
bar: 'boz',
},
{
foo: {
bar: [7, 8, 9],
},
bar: 'biz',
},
)).toEqual({
foo: {
bar: [1, 2, 3, 4, 5, 6, 7, 8, 9],
},
bar: 'biz',
});
});

it('should merge with destruction', () => {
const x = {
foo: 'bar',
Expand Down

0 comments on commit 7d287bf

Please sign in to comment.