Skip to content

fix(utils): treat null as explicit value in mergeDeep, only skip undefined#8204

Open
pbomb wants to merge 4 commits into
ardatan:masterfrom
pbomb:pbomb/fix-merge-deep-null-handling
Open

fix(utils): treat null as explicit value in mergeDeep, only skip undefined#8204
pbomb wants to merge 4 commits into
ardatan:masterfrom
pbomb:pbomb/fix-merge-deep-null-handling

Conversation

@pbomb

@pbomb pbomb commented May 26, 2026

Copy link
Copy Markdown

Summary

Fixes #8197

mergeDeep([{ a: 'foo' }, { a: null }]) now correctly returns { a: null } instead of { a: 'foo' }.

Root cause

The guard if (source == null) { continue; } uses loose equality (==), which skips both null and undefined sources. The intent was to skip undefined (meaning "not provided"), but null is an explicit value and should override earlier values.

Fix

Change source == null to source === undefined. One-line change in packages/utils/src/mergeDeep.ts.

Note on behavior change: mergeDeep([{a: 2}, null]) now returns null rather than {a: 2}. This is the correct semantic — null is an explicit "replace with nothing", distinct from undefined which means "no value provided".

Tests

  • Added two new regression tests in packages/utils/tests/mergeDeep.test.ts
  • All 220 existing utils tests continue to pass

Generated with Claude Code

@changeset-bot

changeset-bot Bot commented May 26, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 3e738d0

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 May 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Treat null values as explicit overrides during object merges, while undefined is treated as absent to avoid unintentionally dropping nulls.
  • Tests
    • Added and clarified unit tests covering null vs undefined merge semantics and override ordering.

Walkthrough

This PR distinguishes undefined (skipped) from null (merged as an explicit override) in mergeDeep, updates tests to assert null override semantics, and adds a changeset documenting a patch release for @graphql-tools/utils.

Changes

Null Value Handling in mergeDeep

Layer / File(s) Summary
Null override fix and validation
.changeset/fix-merge-deep-null-handling.md, packages/utils/src/mergeDeep.ts, packages/utils/tests/mergeDeep.test.ts
The skip condition changes from source == null to source === undefined, preserving null as an explicit property override. Two test cases verify that null values override prior values and are overridden by subsequent values.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Possibly related PRs

  • ardatan/graphql-tools#7012: Modifies the same nullish-check logic in mergeDeep but with different handling of null vs. undefined values.

Suggested reviewers

  • ardatan

Poem

🐰 I hop through code both near and far,
Found a null and gave it star,
No longer hidden, proud and true,
Null now wins when it's its due. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and accurately describes the main change: treating null as an explicit value in mergeDeep while only skipping undefined.
Description check ✅ Passed The description is well-related to the changeset, explaining the root cause, fix, behavioral change, and test additions with clear examples.
Linked Issues check ✅ Passed The PR fully addresses issue #8197 by fixing the null handling regression, providing test cases, and restoring the expected mergeDeep behavior.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the mergeDeep null handling issue: the core fix, test updates, and changesets metadata file.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

@pbomb pbomb marked this pull request as ready for review May 26, 2026 18:04

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (1)
packages/utils/tests/mergeDeep.test.ts (1)

76-78: 💤 Low value

Consider renaming this test for clarity.

The test name "filters nullish values" is now slightly misleading, as the fix ensures that null is no longer filtered—only undefined is. The test itself is correct since it only includes undefined in the test case.

Consider renaming to something like "filters undefined values" or "skips undefined sources" to more accurately reflect the behavior after this fix.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/utils/tests/mergeDeep.test.ts` around lines 76 - 78, Rename the test
description for the it(...) that asserts mergeDeep([{ a: 'dsa' }, { a: 'dd', b:
1 }, undefined]) to accurately reflect behavior (e.g. "filters undefined values"
or "skips undefined sources") so the test name matches the implementation of
mergeDeep; update the string passed to the it call that references mergeDeep in
the test file accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/utils/tests/mergeDeep.test.ts`:
- Around line 76-78: Rename the test description for the it(...) that asserts
mergeDeep([{ a: 'dsa' }, { a: 'dd', b: 1 }, undefined]) to accurately reflect
behavior (e.g. "filters undefined values" or "skips undefined sources") so the
test name matches the implementation of mergeDeep; update the string passed to
the it call that references mergeDeep in the test file accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 640ed839-bfd1-43b2-9c0d-245a796a5970

📥 Commits

Reviewing files that changed from the base of the PR and between 3c93fa9 and 77c85d6.

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

@pbomb

pbomb commented Jun 2, 2026

Copy link
Copy Markdown
Author

Addressed in 3e738d0 — the test was already renamed to 'skips undefined sources' as suggested.

@pbomb

pbomb commented Jun 3, 2026

Copy link
Copy Markdown
Author

@coderabbitai help

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

CodeRabbit commands

These commands are invoked using PR/Issue comments.

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai rate limit to show your current review rate limit status.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai evaluate custom pre-merge check --instructions <custom-checks-instructions> --name <custom-checks-title> [--mode <error|warning>] to validate the custom pre-merge checks instructions. Defaults to error when --mode is omitted.
  • @coderabbitai ignore pre-merge checks to override pre-merge checks and get an approval on PR.
  • @coderabbitai run pre-merge checks to run pre-merge checks on the pull request.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai emit path instructions to emit generated path-specific review instructions for this repository.
  • @coderabbitai resolve merge conflict to automatically resolve merge conflicts.
  • @coderabbitai autofix to automatically fix issues identified in unresolved review comments.
  • @coderabbitai resolve to resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai generate configuration to create a PR that adds the current resolved configuration as .coderabbit.yaml (or show it if already present).
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit configuration file (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, support, documentation and community

  • Visit our status page to check the current availability of CodeRabbit.
  • Create a ticket on our support page for assistance with any issues or questions.
  • Visit our documentation site for detailed information on how to use CodeRabbit.
  • Join our Discord community to connect with other users and get help from the community.
  • Follow us on X/Twitter for updates and announcements.

@pbomb

pbomb commented Jun 9, 2026

Copy link
Copy Markdown
Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (2)
packages/utils/tests/mergeDeep.test.ts (1)

80-86: ⚡ Quick win

Add one regression test for top-level null source overwrite.

The new tests cover property overrides, but they don’t pin the top-level behavior change described by this PR (mergeDeep([{ a: 2 }, null]) === null). Please add that case so the full null-vs-undefined contract is protected.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/utils/tests/mergeDeep.test.ts` around lines 80 - 86, Add a
regression test in packages/utils/tests/mergeDeep.test.ts that asserts
mergeDeep([{ a: 2 }, null]) returns null to protect the top-level
null-vs-undefined contract; locate the existing tests around the mergeDeep spec
(the tests that check property overrides) and add a new it(...) case that calls
mergeDeep with the array [{ a: 2 }, null] and expects the result toEqual(null).
.changeset/fix-merge-deep-null-handling.md (1)

5-5: ⚡ Quick win

Clarify changeset wording for full behavior change.

The note currently mentions property values only. Consider adding that null as a source is now treated as an explicit value too (while only undefined is skipped), to match the shipped behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.changeset/fix-merge-deep-null-handling.md at line 5, Update the changeset
text to explicitly state that mergeDeep now treats null as an explicit source
value (i.e., null will overwrite target properties) while undefined continues to
be skipped; reference the mergeDeep behavior change by name and clarify that
"null property values are treated as explicit overrides rather than skipped" so
readers know null is preserved/assigned and only undefined is ignored.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In @.changeset/fix-merge-deep-null-handling.md:
- Line 5: Update the changeset text to explicitly state that mergeDeep now
treats null as an explicit source value (i.e., null will overwrite target
properties) while undefined continues to be skipped; reference the mergeDeep
behavior change by name and clarify that "null property values are treated as
explicit overrides rather than skipped" so readers know null is
preserved/assigned and only undefined is ignored.

In `@packages/utils/tests/mergeDeep.test.ts`:
- Around line 80-86: Add a regression test in
packages/utils/tests/mergeDeep.test.ts that asserts mergeDeep([{ a: 2 }, null])
returns null to protect the top-level null-vs-undefined contract; locate the
existing tests around the mergeDeep spec (the tests that check property
overrides) and add a new it(...) case that calls mergeDeep with the array [{ a:
2 }, null] and expects the result toEqual(null).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 147991b1-4196-4135-9239-a6e3243b472e

📥 Commits

Reviewing files that changed from the base of the PR and between 3c93fa9 and 3e738d0.

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

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.

mergeDeep behavior changed in 10.10.0 for null values

1 participant