Skip to content

Add case-insensitive map key merge option - #310

Open
AgentGoose32 wants to merge 2 commits into
darccio:masterfrom
AgentGoose32:polar-256-case-insensitive-map-keys
Open

Add case-insensitive map key merge option#310
AgentGoose32 wants to merge 2 commits into
darccio:masterfrom
AgentGoose32:polar-256-case-insensitive-map-keys

Conversation

@AgentGoose32

@AgentGoose32 AgentGoose32 commented Apr 28, 2026

Copy link
Copy Markdown

Fixes #256

Summary

  • Adds WithCaseInsensitiveMapKeys for matching string map keys with strings.EqualFold during merge.
  • Preserves the destination key spelling when a case-insensitive match is found.
  • Applies the option recursively so nested string-keyed maps can merge through differently-cased keys.
  • Leaves default map merge behavior case-sensitive.

Validation

  • go test ./...
  • git diff --check

Summary by CodeRabbit

  • New Features

    • Added an option to enable case-insensitive map key matching during merges so keys match regardless of case while preserving destination key casing.
  • Tests

    • Added tests covering case-insensitive merge behavior, merge with empty-value overwrite, and standard case-sensitive merge behavior when the option is disabled.

@coderabbitai

coderabbitai Bot commented Apr 28, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds a new configuration option WithCaseInsensitiveMapKeys and implements case-insensitive key matching during map merges; updates merge logic (including overwrite-with-empty handling) to resolve source keys to destination keys via case-insensitive comparison. Adds tests covering case-insensitive and default behaviors.

Changes

Cohort / File(s) Summary
Case-Insensitive Map Merge Option
merge.go
Adds WithCaseInsensitiveMapKeys(config *Config) and a caseInsensitiveMapKeys bool field on Config. Implements case-insensitive key resolution in deepMerge for map[string]T merges (matching source keys to existing destination keys using strings.EqualFold), adjusts nil/recursion/slice handling to operate on the resolved destination key, and updates the overwrite-with-empty-src cleanup to perform case-insensitive lookups.
Test Coverage
issue256_test.go
Adds three tests exercising mergo.Merge with mergo.WithCaseInsensitiveMapKeys and combinations of WithOverride / WithOverwriteWithEmptyValue. Tests verify nested and flat map behavior: case-insensitive overwrites produce canonical destination keys, overwrite-with-empty interacts with case-insensitive matching, and default (case-sensitive) behavior keeps distinct keys.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through keys both big and small,
EqualFold made mismatches fall.
Overwrites found their rightful place,
Canonical casing wins the race.
A tiny merge, a happy trace 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the primary change: adding a case-insensitive map key merge option.
Linked Issues check ✅ Passed The pull request fully implements the requirement from issue #256 by adding WithCaseInsensitiveMapKeys option supporting case-insensitive and recursive map key matching via strings.EqualFold.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing case-insensitive map key merging; new tests and config option additions are properly scoped to the stated objective.

✏️ 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
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
issue256_test.go (1)

10-50: Add regression coverage for WithCaseInsensitiveMapKeys + WithOverwriteWithEmptyValue.

Current tests cover override/default behavior well, but not the empty-overwrite cleanup path that should also honor case-insensitive matching. Adding this case will lock the intended behavior.

🧪 Suggested test
+func TestIssue256CaseInsensitiveMapKeysWithOverwriteEmptyValue(t *testing.T) {
+	dst := map[string]string{"Host": "localhost"}
+	src := map[string]string{"host": "example.com"}
+
+	if err := mergo.Merge(&dst, src, mergo.WithOverwriteWithEmptyValue, mergo.WithCaseInsensitiveMapKeys); err != nil {
+		t.Fatal(err)
+	}
+
+	expected := map[string]string{"Host": "example.com"}
+	if !reflect.DeepEqual(dst, expected) {
+		t.Fatalf("got %#v, want %#v", dst, expected)
+	}
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@issue256_test.go` around lines 10 - 50, Add a new test (e.g.,
TestIssue256MergeCaseInsensitiveWithEmptyOverwrite) that mirrors
TestIssue256MergeMapCaseInsensitively but passes
mergo.WithOverwriteWithEmptyValue together with mergo.WithOverride and
mergo.WithCaseInsensitiveMapKeys; create dst with mixed-case keys like
"Config"->"Host": "localhost" and src with same keys in different case (e.g.,
"config"->"host": "") so the empty value must overwrite via case-insensitive
matching, call mergo.Merge(&dst, ...), and assert that the dst "Host" entry
becomes the empty string (and other keys merge as expected) to cover the
empty-overwrite cleanup path honoring case-insensitive keys.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@merge.go`:
- Around line 120-129: The case-insensitive map-key match implemented using
mergeKey (in the loop over src.MapKeys() with config.caseInsensitiveMapKeys) is
not used later when performing overwrite-with-empty cleanup, which still calls
src.MapIndex(key) and can therefore miss the matched key and delete the
newly-merged entry; update the cleanup logic to perform lookups and deletions
using the resolved mergeKey (or otherwise perform the same case-insensitive key
resolution there) so that the same canonical key (mergeKey) is used for both
merging and subsequent src.MapIndex/... deletion; refer to symbols mergeKey,
config.caseInsensitiveMapKeys, src.MapKeys(), and src.MapIndex() (and the
WithCaseInsensitiveMapKeys / WithOverwriteWithEmptyValue behavior) when making
the change.

---

Nitpick comments:
In `@issue256_test.go`:
- Around line 10-50: Add a new test (e.g.,
TestIssue256MergeCaseInsensitiveWithEmptyOverwrite) that mirrors
TestIssue256MergeMapCaseInsensitively but passes
mergo.WithOverwriteWithEmptyValue together with mergo.WithOverride and
mergo.WithCaseInsensitiveMapKeys; create dst with mixed-case keys like
"Config"->"Host": "localhost" and src with same keys in different case (e.g.,
"config"->"host": "") so the empty value must overwrite via case-insensitive
matching, call mergo.Merge(&dst, ...), and assert that the dst "Host" entry
becomes the empty string (and other keys merge as expected) to cover the
empty-overwrite cleanup path honoring case-insensitive keys.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c7e4ed81-d6ac-4bac-8a00-27715c59e548

📥 Commits

Reviewing files that changed from the base of the PR and between bd27904 and ec185a1.

📒 Files selected for processing (2)
  • issue256_test.go
  • merge.go

Comment thread merge.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
merge.go (1)

122-127: ⚠️ Potential issue | 🟠 Major

Prefer exact-key match before EqualFold fallback to avoid nondeterministic merges.

On Line 123-Line 127, the first case-insensitive destination key is chosen by map iteration order. If dst contains both "Host" and "host", a source key "host" can merge into either entry non-deterministically.

💡 Proposed fix
 		for _, key := range src.MapKeys() {
 			mergeKey := key
 			if config.caseInsensitiveMapKeys && key.Kind() == reflect.String && dst.Type().Key().Kind() == reflect.String {
-				for _, dstKey := range dst.MapKeys() {
-					if strings.EqualFold(dstKey.String(), key.String()) {
-						mergeKey = dstKey
-						break
-					}
-				}
+				// Prefer exact-key hit; fallback to case-insensitive lookup.
+				if !dst.MapIndex(key).IsValid() {
+					for _, dstKey := range dst.MapKeys() {
+						if strings.EqualFold(dstKey.String(), key.String()) {
+							mergeKey = dstKey
+							break
+						}
+					}
+				}
 			}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@merge.go` around lines 122 - 127, The current case-insensitive map-key lookup
picks the first EqualFold match from dst.MapKeys(), which is nondeterministic
when dst has keys like "Host" and "host"; modify the logic around
config.caseInsensitiveMapKeys so it first scans dst.MapKeys() for an exact
string match (dstKey.String() == key.String()) and uses that as mergeKey if
found, and only if no exact match exists fall back to the existing
strings.EqualFold check to pick a case-insensitive match; ensure you update the
block that sets mergeKey (the loops over dst.MapKeys()) to implement this
two-pass lookup.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@merge.go`:
- Around line 122-127: The current case-insensitive map-key lookup picks the
first EqualFold match from dst.MapKeys(), which is nondeterministic when dst has
keys like "Host" and "host"; modify the logic around
config.caseInsensitiveMapKeys so it first scans dst.MapKeys() for an exact
string match (dstKey.String() == key.String()) and uses that as mergeKey if
found, and only if no exact match exists fall back to the existing
strings.EqualFold check to pick a case-insensitive match; ensure you update the
block that sets mergeKey (the loops over dst.MapKeys()) to implement this
two-pass lookup.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 30f7aa7b-d268-47df-b7a7-587e34b6dcc4

📥 Commits

Reviewing files that changed from the base of the PR and between ec185a1 and eaf5f0c.

📒 Files selected for processing (2)
  • issue256_test.go
  • merge.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • issue256_test.go

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.

Merge maps case insensitively

1 participant