PropertyNamingPolicy, PropertyNameCaseInsensitive, and Encoder options are honored when serializing and deserializing key-value pairs

JsonSerializer now honors the PropertyNamingPolicy and Encoder options when serializing the Key and Value property names of a KeyValuePair<TKey,TValue> instance. Additionally, JsonSerializer honors the PropertyNamingPolicy and PropertyNameCaseInsensitive options when deserializing KeyValuePair<TKey,TValue> instances.

Change description

Serialization

In .NET Core 3.x versions and in the 4.6.0-4.7.2 versions of the System.Text.Json NuGet package, the properties of KeyValuePair<TKey,TValue> instances are always serialized as "Key" and "Value" exactly, regardless of any JsonSerializerOptions.PropertyNamingPolicy and JsonSerializerOptions.Encoder options. The following code example shows how the Key and Value properties are not camel-cased after serialization, even though the specified property-naming policy dictates so.

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}

Starting in .NET 5, the PropertyNamingPolicy and Encoder options are honored when serializing KeyValuePair<TKey,TValue> instances. The following code example shows how the Key and Value properties are camel-cased after serialization, in accordance with the specified property-naming policy.

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}

Deserialization

In .NET Core 3.x versions and in the 4.7.x versions of the System.Text.Json NuGet package, a JsonException is thrown when the JSON property names are not precisely Key and Value, for example, if they don't start with an uppercase letter. The exception is thrown even if a specified property-naming policy expressly permits it.

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

Starting in .NET 5, the PropertyNamingPolicy and PropertyNameCaseInsensitive options are honored when deserializing using JsonSerializer. For example, the following code snippet shows successful deserialization of lowercased Key and Value property names because the specified property-naming policy permits it.

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

To accommodate payloads that were serialized with previous versions, "Key" and "Value" are special-cased to match when deserializing. Even though the Key and Value property names aren't camel-cased according to the in PropertyNamingPolicy option in the following code example, they deserialize successfully.

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

Version introduced

5.0

Reason for change

Substantial customer feedback indicated that the PropertyNamingPolicy should be honored. For completeness, the PropertyNameCaseInsensitive and Encoder options are also honored, so that KeyValuePair<TKey,TValue> instances are treated the same as any other plain old CLR object (POCO).

If this change is disruptive to you, you can use a custom converter that implements the desired semantics.

Affected APIs