Skip to content

feat(collections): add "union" to DeepMergeOptions.arrays#7049

Open
DonBLong wants to merge 1 commit intodenoland:mainfrom
DonBLong:feat/collections/deep-merge/arrays-union
Open

feat(collections): add "union" to DeepMergeOptions.arrays#7049
DonBLong wants to merge 1 commit intodenoland:mainfrom
DonBLong:feat/collections/deep-merge/arrays-union

Conversation

@DonBLong
Copy link
Copy Markdown

Adds a "union" MergingStrategy for arrays only, through a new exported type ArraysMergingStrategy. Implementation uses the union function imported from "./union.ts"

closes: #7048

@DonBLong DonBLong requested a review from kt3k as a code owner March 13, 2026 16:05
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Mar 13, 2026

CLA assistant check
All committers have signed the CLA.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 13, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.38%. Comparing base (d2fb2d6) to head (462218c).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #7049   +/-   ##
=======================================
  Coverage   94.38%   94.38%           
=======================================
  Files         628      628           
  Lines       50178    50181    +3     
  Branches     8840     8841    +1     
=======================================
+ Hits        47360    47363    +3     
  Misses       2251     2251           
  Partials      567      567           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@DonBLong DonBLong force-pushed the feat/collections/deep-merge/arrays-union branch 2 times, most recently from 444e16e to d30e9e7 Compare March 16, 2026 12:20
Copy link
Copy Markdown
Member

@bartlomieju bartlomieju left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new unstable_deep_merge.ts (598 lines) is a near-verbatim copy of the existing deep_merge.ts (592 lines). The only meaningful differences are:

  1. import { union } from "./union.ts" added
  2. ArraysMergingStrategy type added (MergingStrategy | "union")
  3. DeepMergeOptions.arrays changed from MergingStrategy to ArraysMergingStrategy
  4. 3 lines in mergeObjects() to handle the "union" case

Everything else — function signatures, JSDoc, all type utilities, helper functions — is duplicated. Maintaining two near-identical 600-line files is not sustainable.

This should instead modify the existing deep_merge.ts directly, since the change is purely additive (adding a new valid string literal to the arrays option). Existing callers are unaffected.

Additional issues

JSDoc examples import from wrong path — All doc examples import from "@std/collections/deep-merge" instead of "@std/collections/unstable-deep-merge", so doc tests would either fail or test the wrong module.

Merge type doesn't handle "union" for arrays — The Merge type only checks for { arrays: "replace" }. When arrays: "union" is passed, the type falls through to MergeAllArrays<T, U>, which happens to be correct by coincidence, but the intent isn't expressed.

Test file also duplicates existing tests — The test file (494 lines) copies most tests from deep_merge_test.ts. Only ~2 tests are actually new (the "union" merge test and the default strategy test).

Suggestion

Instead of a new file, add the "union" strategy to the existing deep_merge.ts:

  1. Add ArraysMergingStrategy = MergingStrategy | "union"
  2. Change DeepMergeOptions.arrays type to ArraysMergingStrategy
  3. Add the 3-line union branch in mergeObjects()
  4. Add the union-specific tests to the existing test file

If it needs to stay unstable, the new type can be exported from an unstable_ entrypoint that re-exports and extends the stable module.

Adds a `"union"` `MergingStrategy` for `arrays` only, through a new exported type `ArraysMergingStrategy`.
Implementation uses the `union` `function` imported from `"./union.ts"`
@DonBLong DonBLong force-pushed the feat/collections/deep-merge/arrays-union branch from d30e9e7 to 462218c Compare March 25, 2026 17:29
@DonBLong DonBLong changed the title feat(collections/unstable): add "union" to DeepMergeOptions.arrays feat(collections): add "union" to DeepMergeOptions.arrays Mar 25, 2026
@DonBLong
Copy link
Copy Markdown
Author

Maintaining two near-identical 600-line files is not sustainable

My bad. It's my first time contributing to this repo and I have misunderstood the contributing guides. I thought "new APIs" meant any addition to a stable API. I also thought that the maintainers would want to compare the diff between the unstable_ files and the stable ones, which is why I copy-pasted the stable ones instead of extending.

I took your first suggestion, and applied the changes to the original deep_merge.ts and deep_merge_test.ts and also removed the exported entry unstable-deep-merge from deno.json.

I also modified the commit message to reflect that the feature is being added directly to the stable collections scope instead of collections/unstable.

@DonBLong
Copy link
Copy Markdown
Author

DonBLong commented Mar 25, 2026

Merge type doesn't handle "union" for arrays

Note

TL;DR

The reason why I didn't touch the Merge type, is because it actually aligns more with the union strategy than with the concat method, since types cannot concatenate. So, I believe the intent is expressed correctly here, because the Merge type basically says that "replace" = PartialByType<U, Array<unknown>> which is right side, and any other merging strategy falls through to a union of all types.


About that though

It's funny that you mentioned this type, because that's what confused me about the return of deepMerge in the first place. The problem is, types only know union. For example:

const a = { foo: [1, 2, 3] };

const b = { foo: [3, 4, 5] };

const abFoo = a.foo.concat(b.foo);

The type of abFoo is just a number[], because neither a.foo nor b.foo are readonly so they both have the same type. And even if b.foo was readonly, abFoo would still be just a number[] because number devours whatever specific numbers b.foo would have.

However, according to the Merge type, the following is also possible:

const a = { foo: [1, 2, 3] };

const b = { foo: ["string", 4, 5] };

const abFoo = a.foo.concat(b.foo); // what deepMerge(a, b, { arrays: "merge" }) does internally

Because the type of a.foo and b.foo inside of the deepMerge call are both immediately converted to (string | number)[]. And because I know that a.foo.concat(b.foo) would throw the following TypeScript error:

Type '(string | number)[]' is not assignable to type 'number[]'

I thought that deepMerge is probably using the spread syntax to merge the arrays, which would not result in a TypeScript error, but would also not remove duplicates. Or, that it is using union, and by union I mean either @std/collections/union or the new Set.union method, which would also not throw a TypeScript error in this case, but would also remove the duplicates from both a.foo and b.foo:

const a = { foo: [1, 2, 3] };

const b = { foo: ["string", 3, 4] };

const abFoo = union(a.foo, b.foo);

// OR: const abFoo = Array.from(new Set(a.foo).union(new Set(b.foo)));

assertEquals(abFoo, [1, 2, 3, "string", 4]);

Conclusion

I think the confusion comes from the name "merge", because it doesn't state exactly the type of merge the function is doing. Is it a union, an intersection, a difference, and in case of arrays a concat? Which why my other solution in #7048 was to rename the option "merge" to what it really does, "concat" for arrays and "union" for Sets and Maps.

@DonBLong DonBLong requested a review from bartlomieju March 25, 2026 19:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add "union" MergingStrategy to DeepMergeOptions.arrays in @std/collections/deep-merge

3 participants