Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit a24db0b

Browse files
authored
Honor dictionary key policy when entry value is null (#42267)
* Honor dictionary key policy when entry value is null * Address review feedback * Remove reverse Json checks
1 parent 4410ec0 commit a24db0b

File tree

3 files changed

+102
-21
lines changed

3 files changed

+102
-21
lines changed

src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections;
66
using System.Collections.Generic;
77
using System.Diagnostics;
8+
using System.Text.Json.Serialization;
89

910
namespace System.Text.Json
1011
{
@@ -109,22 +110,26 @@ protected override void OnWriteDictionary(ref WriteStackFrame current, Utf8JsonW
109110

110111
Debug.Assert(key != null);
111112

113+
if (Options.DictionaryKeyPolicy != null)
114+
{
115+
// We should not be in the Nullable-value implementation branch for extension data.
116+
// (TValue should be typeof(object) or typeof(JsonElement)).
117+
Debug.Assert(current.ExtensionDataStatus != ExtensionDataWriteStatus.Writing);
118+
119+
key = Options.DictionaryKeyPolicy.ConvertName(key);
120+
121+
if (key == null)
122+
{
123+
ThrowHelper.ThrowInvalidOperationException_SerializerDictionaryKeyNull(Options.DictionaryKeyPolicy.GetType());
124+
}
125+
}
126+
112127
if (value == null)
113128
{
114129
writer.WriteNull(key);
115130
}
116131
else
117132
{
118-
if (Options.DictionaryKeyPolicy != null)
119-
{
120-
key = Options.DictionaryKeyPolicy.ConvertName(key);
121-
122-
if (key == null)
123-
{
124-
ThrowHelper.ThrowInvalidOperationException_SerializerDictionaryKeyNull(Options.DictionaryKeyPolicy.GetType());
125-
}
126-
}
127-
128133
writer.WritePropertyName(key);
129134
Converter.Write(writer, value.GetValueOrDefault(), Options);
130135
}

src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleDictionary.cs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -145,23 +145,26 @@ internal static void WriteDictionary<TProperty>(
145145
current.JsonPropertyInfo.PropertyInfo);
146146
}
147147

148+
Debug.Assert(key != null);
149+
150+
if (options.DictionaryKeyPolicy != null &&
151+
// We do not convert extension data.
152+
current.ExtensionDataStatus != ExtensionDataWriteStatus.Writing)
153+
{
154+
key = options.DictionaryKeyPolicy.ConvertName(key);
155+
156+
if (key == null)
157+
{
158+
ThrowHelper.ThrowInvalidOperationException_SerializerDictionaryKeyNull(options.DictionaryKeyPolicy.GetType());
159+
}
160+
}
161+
148162
if (value == null)
149163
{
150164
writer.WriteNull(key);
151165
}
152166
else
153167
{
154-
if (options.DictionaryKeyPolicy != null &&
155-
current.ExtensionDataStatus != ExtensionDataWriteStatus.Writing) // We do not convert extension data.
156-
{
157-
key = options.DictionaryKeyPolicy.ConvertName(key);
158-
159-
if (key == null)
160-
{
161-
ThrowHelper.ThrowInvalidOperationException_SerializerDictionaryKeyNull(options.DictionaryKeyPolicy.GetType());
162-
}
163-
}
164-
165168
writer.WritePropertyName(key);
166169
converter.Write(writer, value, options);
167170
}

src/System.Text.Json/tests/Serialization/DictionaryTests.KeyPolicy.cs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,29 @@ public static void CamelCaseDeserialize()
4646
Assert.Equal(4, obj[1]["Key2"]);
4747
}
4848

49+
[Fact]
50+
public static void IgnoreKeyPolicyForExtensionData()
51+
{
52+
var options = new JsonSerializerOptions
53+
{
54+
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase // e.g. Key1 -> key1.
55+
};
56+
57+
// Ensure we ignore key policy for extension data and deserialize keys as they are.
58+
ClassWithExtensionData myClass = JsonSerializer.Deserialize<ClassWithExtensionData>(@"{""Key1"":1, ""Key2"":2}", options);
59+
Assert.Equal(1, (myClass.ExtensionData["Key1"]).GetInt32());
60+
Assert.Equal(2, (myClass.ExtensionData["Key2"]).GetInt32());
61+
62+
// Ensure we ignore key policy for extension data and serialize keys as they are.
63+
Assert.Equal(@"{""Key1"":1,""Key2"":2}", JsonSerializer.Serialize(myClass, options));
64+
}
65+
66+
public class ClassWithExtensionData
67+
{
68+
[JsonExtensionData]
69+
public Dictionary<string, JsonElement> ExtensionData { get; set; }
70+
}
71+
4972
[Fact]
5073
public static void CamelCaseSerialize()
5174
{
@@ -72,6 +95,56 @@ public static void CamelCaseSerialize()
7295
Assert.Equal(JsonCamel, json);
7396
}
7497

98+
[Fact]
99+
public static void CamelCaseSerialize_Null_Values()
100+
{
101+
var options = new JsonSerializerOptions()
102+
{
103+
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase // e.g. Key1 -> key1.
104+
};
105+
106+
Dictionary<string, string>[] obj = new Dictionary<string, string>[]
107+
{
108+
new Dictionary<string, string>() { { "Key1", null }, { "Key2", null } },
109+
};
110+
111+
const string Json = @"[{""Key1"":null,""Key2"":null}]";
112+
const string JsonCamel = @"[{""key1"":null,""key2"":null}]";
113+
114+
// Without key policy option, serialize keys as they are.
115+
string json = JsonSerializer.Serialize<object>(obj);
116+
Assert.Equal(Json, json);
117+
118+
// With key policy option, serialize keys with camel casing.
119+
json = JsonSerializer.Serialize<object>(obj, options);
120+
Assert.Equal(JsonCamel, json);
121+
}
122+
123+
[Fact]
124+
public static void CamelCaseSerialize_Null_Nullable_Values()
125+
{
126+
var options = new JsonSerializerOptions()
127+
{
128+
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase // e.g. Key1 -> key1.
129+
};
130+
131+
Dictionary<string, int?>[] obj = new Dictionary<string, int?>[]
132+
{
133+
new Dictionary<string, int?>() { { "Key1", null }, { "Key2", null } },
134+
};
135+
136+
const string Json = @"[{""Key1"":null,""Key2"":null}]";
137+
const string JsonCamel = @"[{""key1"":null,""key2"":null}]";
138+
139+
// Without key policy option, serialize keys as they are.
140+
string json = JsonSerializer.Serialize<object>(obj);
141+
Assert.Equal(Json, json);
142+
143+
// With key policy option, serialize keys with camel casing.
144+
json = JsonSerializer.Serialize<object>(obj, options);
145+
Assert.Equal(JsonCamel, json);
146+
}
147+
75148
[Fact]
76149
public static void CustomNameDeserialize()
77150
{

0 commit comments

Comments
 (0)