Skip to content

fix(compat/mergeWith): treat a trailing non-function argument as a source - #2070

Open
ilievskizoran wants to merge 3 commits into
toss:mainfrom
ilievskizoran:main
Open

fix(compat/mergeWith): treat a trailing non-function argument as a source#2070
ilievskizoran wants to merge 3 commits into
toss:mainfrom
ilievskizoran:main

Conversation

@ilievskizoran

Copy link
Copy Markdown

Summary

es-toolkit/compat/mergeWith differs in behaviour to lodash with regards to the customizer.

lodash checks the last argument is a function, and treats it as another source if it is not, performing a standard merge.
es-toolkit does not have the function check, and does two things based on arguments:

  • Two arguments - Swallows the second argument and essentially returns the target.
  • Three + arguments - Throws an error as it invokes the last argument as a function.
import { mergeWith as esToolkitMergeWith} from 'es-toolkit/compat';
import { mergeWith as lodashMergeWith } from 'lodash';

// Two Arguments
lodashMergeWith({}, { a: 1 });  // { a: 1 }
esToolkitMergeWith({}, { a: 1 }); // {}

// Three Arguments
lodashMergeWith({}, { a: 1 }, {b: 2}); // { a: 1, b: 2 } 
esToolkitMergeWith({}, { a: 1 }, {b: 2}); // TypeError: merge is not a function

Changes

  • Add a typeof function check for the last argument.
  • If the last argument is not a function, it defaults the customizer to noop.

compat/merge essentially already implements this behaviour:

export function merge(object: any, ...sources: any[]): any {
  return mergeWith(object, ...sources, noop);
}

Benchmark results

main

✓ |benchmarks| mergeWith.bench.ts > mergeWith 5613ms
     name                                    hz     min     max    mean     p75     p99    p995    p999     rme  samples
   · lodash/mergeWith              6,644,952.62  0.0001  0.7230  0.0002  0.0002  0.0003  0.0003  0.0005  ±0.63%  3322477
   · es-toolkit/mergeWith         16,548,747.27  0.0000  0.0454  0.0001  0.0001  0.0001  0.0001  0.0002  ±0.07%  8274374
   · es-toolkit/compat/mergeWith   5,692,704.71  0.0001  0.1372  0.0002  0.0002  0.0003  0.0003  0.0004  ±0.31%  2846353

 BENCH  Summary

  |benchmarks| es-toolkit/mergeWith - mergeWith.bench.ts > mergeWith
    2.49x faster than lodash/mergeWith
    2.91x faster than es-toolkit/compat/mergeWith

PR

✓ |benchmarks| mergeWith.bench.ts > mergeWith 5742ms
     name                                    hz     min     max    mean     p75     p99    p995    p999     rme  samples
   · lodash/mergeWith              6,796,597.99  0.0001  0.5586  0.0001  0.0002  0.0002  0.0003  0.0004  ±0.59%  3398299
   · es-toolkit/mergeWith         16,257,090.67  0.0000  0.0597  0.0001  0.0001  0.0001  0.0001  0.0002  ±0.07%  8128546
   · es-toolkit/compat/mergeWith   5,641,282.91  0.0001  0.2366  0.0002  0.0002  0.0003  0.0003  0.0004  ±0.38%  2820642

 BENCH  Summary

  |benchmarks| es-toolkit/mergeWith - mergeWith.bench.ts > mergeWith
    2.39x faster than lodash/mergeWith
    2.88x faster than es-toolkit/compat/mergeWith
  • The difference is written as code: the input, what Lodash returns for it, and what es-toolkit returns today.
  • A test that fails without this change is included, so the behavior does not drift back later.
  • Benchmark results are attached if the function runs in a hot path.

Copilot AI lite review requested due to automatic review settings August 30, 2026 01:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The updated documentation code snippets use invalid customizer? syntax in a function call and should be corrected.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR fixes es-toolkit/compat’s mergeWith to match Lodash behavior by only treating the last argument as a customizer when it’s a function, otherwise merging it as an additional source.

Changes:

  • Update compat/mergeWith runtime behavior to detect whether a trailing customizer is callable and default to noop when it isn’t.
  • Add test coverage ensuring trailing non-function arguments are merged as sources and trailing functions are still treated as customizers.
  • Update compat docs (all languages) to describe the “customizer is optional / only if function” behavior (but the top snippet currently uses invalid customizer? call syntax).
File summaries
File Description
src/compat/object/mergeWith.ts Detects whether the last argument is a function; merges all args as sources when no customizer is provided and uses noop as the default customizer.
src/compat/object/mergeWith.spec.ts Adds regression tests for trailing non-function vs function last-argument handling, plus default deep-merge behavior without a customizer.
docs/compat/reference/object/mergeWith.md Documents optional customizer behavior; top snippet needs valid TypeScript call syntax.
docs/ko/compat/reference/object/mergeWith.md Same documentation update as English; top snippet needs valid TypeScript call syntax.
docs/ja/compat/reference/object/mergeWith.md Same documentation update as English; top snippet needs valid TypeScript call syntax.
docs/zh_hans/compat/reference/object/mergeWith.md Same documentation update as English; top snippet needs valid TypeScript call syntax.
Review details
  • Files reviewed: 6/6 changed files
  • Comments generated: 4
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread docs/compat/reference/object/mergeWith.md Outdated
Comment thread docs/ko/compat/reference/object/mergeWith.md Outdated
Comment thread docs/ja/compat/reference/object/mergeWith.md Outdated
Comment thread docs/zh_hans/compat/reference/object/mergeWith.md Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants