Fix duplicate key handling#10049
Merged
NikolaMilosavljevic merged 1 commit intodotnet:mainfrom Mar 25, 2026
Merged
Conversation
MiYanni
approved these changes
Mar 25, 2026
mmitche
pushed a commit
to dotnet/sdk
that referenced
this pull request
Apr 2, 2026
Fixes dotnet/templating#10025 ## Problem After migrating from Newtonsoft.Json to System.Text.Json, loading template.json files with duplicate case-insensitive property keys (e.g. `"Empty"` and `"empty"`) throws an `ArgumentException`: ``` System.ArgumentException: An item with the same key has already been added. Key: empty (Parameter 'key') at System.Collections.Generic.OrderedDictionary`2.Add(TKey key, TValue value) at System.Text.Json.Nodes.JsonObject.InitializeDictionary() at System.Text.Json.Nodes.JsonObject.get_Count() ... at Microsoft.TemplateEngine.Orchestrator.RunnableProjects.ConfigModel.TemplateConfigModel..ctor(...) ``` This affected MAUI templates (`maui-blazor`, `maui-blazor-solution`) which intentionally had both `"Empty"` (PascalCase backward-compat alias) and `"empty"` (lowercase) symbol definitions. ## Root Cause In `src/Shared/JExtensions.cs`, the `NodeOptions` field was configured with `PropertyNameCaseInsensitive = true`: ```csharp private static readonly JsonNodeOptions NodeOptions = new() { PropertyNameCaseInsensitive = true }; ``` This caused `JsonObject` to use a case-insensitive internal dictionary. When any operation triggered `JsonObject.InitializeDictionary()` (e.g. accessing `.Count`, iterating via `.ToList()`), it attempted to insert both `"Empty"` and `"empty"` into the same case-insensitive dictionary, throwing on the duplicate. This setting was **redundant** — all case-insensitive property lookups in the codebase already go through the `GetPropertyCaseInsensitive()` helper method, which manually performs a case-insensitive fallback search. ## Fix **File changed:** `src/Shared/JExtensions.cs` - Removed the `NodeOptions` field entirely (it only existed to set `PropertyNameCaseInsensitive = true`) - Replaced all 6 usages of `NodeOptions` in `JsonNode.Parse()` calls with `null` (uses default `JsonNodeOptions`, which is case-sensitive) This means `JsonObject` now stores properties with their original casing and uses a case-sensitive internal dictionary — which tolerates keys like `"Empty"` and `"empty"` coexisting. Case-insensitive lookups continue to work through the existing `GetPropertyCaseInsensitive()` helper. ## Test Added **File:** `test/Microsoft.TemplateEngine.Orchestrator.RunnableProjects.UnitTests/TemplateConfigTests/GenericTests.cs` Added `CanReadTemplateWithDuplicateCaseInsensitiveSymbolKeys` — a regression test that verifies a template.json with symbols `"Empty"` and `"empty"` loads without throwing. Commit migrated from dotnet/templating@791a389
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #10025
Problem
After migrating from Newtonsoft.Json to System.Text.Json, loading template.json files with duplicate case-insensitive property keys (e.g.
"Empty"and"empty") throws anArgumentException:This affected MAUI templates (
maui-blazor,maui-blazor-solution) which intentionally had both"Empty"(PascalCase backward-compat alias) and"empty"(lowercase) symbol definitions.Root Cause
In
src/Shared/JExtensions.cs, theNodeOptionsfield was configured withPropertyNameCaseInsensitive = true:This caused
JsonObjectto use a case-insensitive internal dictionary. When any operation triggeredJsonObject.InitializeDictionary()(e.g. accessing.Count, iterating via.ToList()), it attempted to insert both"Empty"and"empty"into the same case-insensitive dictionary, throwing on the duplicate.This setting was redundant — all case-insensitive property lookups in the codebase already go through the
GetPropertyCaseInsensitive()helper method, which manually performs a case-insensitive fallback search.Fix
File changed:
src/Shared/JExtensions.csNodeOptionsfield entirely (it only existed to setPropertyNameCaseInsensitive = true)NodeOptionsinJsonNode.Parse()calls withnull(uses defaultJsonNodeOptions, which is case-sensitive)This means
JsonObjectnow stores properties with their original casing and uses a case-sensitive internal dictionary — which tolerates keys like"Empty"and"empty"coexisting. Case-insensitive lookups continue to work through the existingGetPropertyCaseInsensitive()helper.Test Added
File:
test/Microsoft.TemplateEngine.Orchestrator.RunnableProjects.UnitTests/TemplateConfigTests/GenericTests.csAdded
CanReadTemplateWithDuplicateCaseInsensitiveSymbolKeys— a regression test that verifies a template.json with symbols"Empty"and"empty"loads without throwing.