Skip to content

fix(utils): treat explicit undefined as an override in mergeDeep#8302

Merged
enisdenjo merged 3 commits into
ardatan:masterfrom
pbomb:pbomb/fix-merge-deep-undefined-handling
Jul 17, 2026
Merged

fix(utils): treat explicit undefined as an override in mergeDeep#8302
enisdenjo merged 3 commits into
ardatan:masterfrom
pbomb:pbomb/fix-merge-deep-undefined-handling

Conversation

@pbomb

@pbomb pbomb commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #8204, which made mergeDeep treat an explicit null property value as an override rather than skipping it. This does the same for an explicit undefined property value, while keeping it distinct from the property being absent entirely.

Before this change, mergeDeep([{ a: 'foo' }, { a: undefined }]) returned { a: 'foo' } — the explicit undefined had no effect, indistinguishable from mergeDeep([{ a: 'foo' }, {}]). This is surprising for consumers (e.g. test mock builders) that rely on undefined to explicitly clear/override a field.

Details

The by-key merge loop recursed into mergeDeep([output[key], source[key]]) for every key present on source, including ones whose value was explicitly undefined. That recursive call re-triggered the top-level if (source === undefined) continue, silently discarding the override and keeping the prior value — the same skip that intentionally lets a whole undefined source in the top-level sources array be ignored (e.g. mergeDeep([defaults, options]) where options wasn't provided).

The fix only recurses when the incoming value isn't undefined; an explicit undefined is assigned directly. This keeps the "whole source is undefined" skip (still exercised by the existing 'skips undefined sources' test) fully intact, while making an explicitly-present-but-undefined key override like null does. A key that's absent from source is never visited by for...in, so it's naturally left untouched — distinct from the explicit-undefined case, per the added tests.

Tests

  • Added a failing-first unit test asserting an explicit undefined property value overrides a previously merged value ('a' in merged is true and merged.a is undefined).
  • Added a companion test asserting a merely-absent property is left untouched, to make the "distinct from absence" contract explicit.
  • Ran the full utils and merge package test suites (353 tests) — all passing.

pbomb added 3 commits July 17, 2026 14:13
…mergeDeep

Previously the top-level `if (source === undefined) continue` (added for
PR ardatan#8204's null handling) also fired when a key's value was explicitly
undefined, since the by-key merge recursed through the same mergeDeep
call. That silently discarded the override and kept the prior value,
indistinguishable from the key being absent entirely.

Now the by-key merge only recurses when the incoming value isn't
undefined; an explicit undefined is assigned directly, so it overrides
like null does, while an absent key (never visited by `for...in`) is
still left untouched.
@changeset-bot

changeset-bot Bot commented Jul 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2b28751

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 26 packages
Name Type
@graphql-tools/utils Patch
@graphql-tools/executor Patch
@graphql-tools/graphql-tag-pluck Patch
@graphql-tools/import Patch
@graphql-tools/links Patch
@graphql-tools/load Patch
@graphql-tools/merge Patch
@graphql-tools/mock Patch
@graphql-tools/node-require Patch
@graphql-tools/relay-operation-optimizer Patch
@graphql-tools/resolvers-composition Patch
@graphql-tools/schema Patch
@graphql-tools/apollo-engine-loader Patch
@graphql-tools/code-file-loader Patch
@graphql-tools/git-loader Patch
@graphql-tools/github-loader Patch
@graphql-tools/graphql-file-loader Patch
@graphql-tools/json-file-loader Patch
@graphql-tools/module-loader Patch
@graphql-tools/url-loader Patch
@graphql-tools/executor-apollo-link Patch
@graphql-tools/executor-envelop Patch
@graphql-tools/executor-legacy-ws Patch
@graphql-tools/executor-urql-exchange Patch
@graphql-tools/executor-yoga Patch
graphql-tools Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0977a93f-2ffc-405c-ab94-768c1709f197

📥 Commits

Reviewing files that changed from the base of the PR and between 3c27f9a and 2b28751.

📒 Files selected for processing (3)
  • .changeset/fix-merge-deep-undefined-handling.md
  • packages/utils/src/mergeDeep.ts
  • packages/utils/tests/mergeDeep.test.ts

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Fixed deep merge behavior so explicitly provided undefined values override existing properties.
    • Preserved existing values when a property is omitted from a later source.
    • Added coverage for both scenarios to ensure consistent merge results.

Walkthrough

mergeDeep now treats an explicitly present undefined source property as an override while preserving earlier values when properties are omitted. Tests cover both cases, and a patch changeset documents the behavior.

Changes

mergeDeep undefined handling

Layer / File(s) Summary
Explicit undefined merge behavior
packages/utils/src/mergeDeep.ts, packages/utils/tests/mergeDeep.test.ts, .changeset/fix-merge-deep-undefined-handling.md
mergeDeep directly assigns source properties whose value is undefined; tests distinguish explicit undefined from omitted properties, and the changeset documents the patch.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers: enisdenjo

Poem

I’m a rabbit with a merge to mend,
Undefined now arrives as a friend.
Missing keys leave old values in place,
Tests mark each case with careful grace.
Hop, hop—patch released!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: treating explicit undefined as an override in mergeDeep.
Description check ✅ Passed The description accurately explains the mergeDeep undefined-override fix and added tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pbomb
pbomb marked this pull request as ready for review July 17, 2026 20:25
@pbomb

pbomb commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

@enisdenjo This PR is similar to #8204 which you just approved. I didn't realize that this issue was also a blocking issue for me, but uncovered after upgrading to the latest release.

@enisdenjo

Copy link
Copy Markdown
Collaborator

thank you for the PR! will be releasing sooon 🙏

@enisdenjo
enisdenjo merged commit e90719b into ardatan:master Jul 17, 2026
70 of 71 checks passed
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