Skip to content

Breaking change: PropertyNamingPolicy, PropertyNameCaseInsensitive, & Encoder options are honored when (de)serializing KeyValuePair instances with JsonSerializer #20898

@layomia

Description

@layomia

PropertyNamingPolicy, PropertyNameCaseInsensitive, & Encoder options are honored when (de)serializing KeyValuePair instances with JsonSerializer

JsonSerializer will potentially serialize the "Key" and "Value" property names differently than in .NET Core 3.x/System.Text.Json 4.7.x (as influenced by the naming policy & encoder). When deserializing, the PropertyNamingPolicy and PropertyNameCaseInsensitive are honored. However, roundtripping is not broken as the literals "Key" and "Value" are special-cased to match when deserializing, to accommodate payloads that were serialized with previous versions.

Version introduced

.NET 5.0

Old behavior

On serialization:

The properties of KeyValuePair instances are always serialized as the literal "Key" and "Value", regardless of aforementioned options.

var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
KeyValuePair<int, int> kvp = KeyValuePair.Create(1, 1);
Console.WriteLine(JsonSerializer.Serialize(kvp, options));
// Expected: {"key":1,"value":1}
// Actual: {"Key":1,"Value":1}

On deserialization:

JsonException is thrown when the JSON property names are not precisely the "Key" and "Value" literals.

var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
string json = @"{""key"":1,""value"":1}";
JsonSerializer.Deserialize<KeyValuePair<int, int>>(json, options); // JsonException thrown

New behavior

The aforementioned options are honored when serializing KeyValuePair instances.

var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
KeyValuePair<int, int> kvp = KeyValuePair.Create(1, 1);
Console.WriteLine(JsonSerializer.Serialize(kvp, options));
// {"key":1,"value":1}

On deserialization:

PropertyNamingPolicy and PropertyNameCaseInsensitive options are honored:

var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
string json = @"{""key"":1,""value"":1}";

KeyValuePair<int, int> kvp = JsonSerializer.Deserialize<KeyValuePair<int, int>>(json);
Console.WriteLine(kvp.Key); // 1
Console.WriteLine(kvp.Value); // 1

Note that the literals "Key" and "Value" are special-cased to match when deserializing, to accommodate payloads that were serialized with previous versions:

var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
string json = @"{""Key"":1,""Value"":1}";

KeyValuePair<int, int> kvp = JsonSerializer.Deserialize<KeyValuePair<int, int>>(json);
Console.WriteLine(kvp.Key); // 1
Console.WriteLine(kvp.Value); // 1

Reason for change

Substantial customer feedback indicated that the PropertyNamingPolicy should be honored. For completeness, the PropertyNameCaseInsensitive & Encoder options are also honored so that KeyValuePair<,> instances are treated the same as any other POCO.

Recommended action

For folks who find this change disruptive, the recommendation is to use a custom converter that implements the desired semantics.

Category

  • ASP.NET Core
  • C#
  • Code analysis
  • Core .NET libraries
  • Cryptography
  • Data
  • Debugger
  • Deployment
  • Globalization
  • Interop
  • JIT
  • LINQ
  • Managed Extensibility Framework (MEF)
  • MSBuild
  • Networking
  • Printing
  • Security
  • Serialization
  • Visual Basic
  • Windows Forms
  • Windows Presentation Foundation (WPF)
  • XML, XSLT

Affected APIs


Issue metadata

  • Issue type: breaking-change

Metadata

Metadata

Assignees

Labels

🏁 Release: .NET 5Work items for the .NET 5 releasebreaking-changeIndicates a .NET Core breaking change

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions