Skip to content

Fix duplicate key handling#10049

Merged
NikolaMilosavljevic merged 1 commit intodotnet:mainfrom
NikolaMilosavljevic:fix1
Mar 25, 2026
Merged

Fix duplicate key handling#10049
NikolaMilosavljevic merged 1 commit intodotnet:mainfrom
NikolaMilosavljevic:fix1

Conversation

@NikolaMilosavljevic
Copy link
Copy Markdown
Member

@NikolaMilosavljevic NikolaMilosavljevic commented Mar 25, 2026

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 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:

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.

@NikolaMilosavljevic NikolaMilosavljevic merged commit 791a389 into dotnet:main Mar 25, 2026
8 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.

Template creation throws an exception: dotnet new android, dotnet new maui-blazor

2 participants