diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx
index b34b706d927507..44de8a1f85fb79 100644
--- a/src/libraries/System.Text.Json/src/Resources/Strings.resx
+++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx
@@ -396,8 +396,8 @@
The converter '{0}' wrote too much or not enough.
-
- The dictionary key policy '{0}' cannot return a null key.
+
+ The naming policy '{0}' cannot return null.
The attribute '{0}' cannot exist more than once on '{1}'.
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryDefaultConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryDefaultConverter.cs
index a657398ca2d3f8..4603b59c82f960 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryDefaultConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryDefaultConverter.cs
@@ -47,7 +47,7 @@ protected string GetKeyName(string key, ref WriteStack state, JsonSerializerOpti
if (key == null)
{
- ThrowHelper.ThrowInvalidOperationException_SerializerDictionaryKeyNull(options.DictionaryKeyPolicy.GetType());
+ ThrowHelper.ThrowInvalidOperationException_NamingPolicyReturnNull(options.DictionaryKeyPolicy);
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/KeyValuePairConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/KeyValuePairConverter.cs
index f74a7de0cdbe1e..08e790738f62fa 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/KeyValuePairConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/KeyValuePairConverter.cs
@@ -3,23 +3,52 @@
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
+using System.Text.Encodings.Web;
namespace System.Text.Json.Serialization.Converters
{
internal sealed class KeyValuePairConverter : JsonValueConverter>
{
- private const string KeyName = "Key";
- private const string ValueName = "Value";
+ private const string KeyNameCLR = "Key";
+ private const string ValueNameCLR = "Value";
- // todo: https://github.com/dotnet/runtime/issues/1197
- // move these to JsonSerializerOptions and use the proper encoding.
- private static readonly JsonEncodedText _keyName = JsonEncodedText.Encode(KeyName, encoder: null);
- private static readonly JsonEncodedText _valueName = JsonEncodedText.Encode(ValueName, encoder: null);
+ // Property name for "Key" and "Value" with Options.PropertyNamingPolicy applied.
+ private string _keyName = null!;
+ private string _valueName = null!;
+
+ // _keyName and _valueName as JsonEncodedText.
+ private JsonEncodedText _keyNameEncoded;
+ private JsonEncodedText _valueNameEncoded;
// todo: https://github.com/dotnet/runtime/issues/32352
// it is possible to cache the underlying converters since this is an internal converter and
// an instance is created only once for each JsonSerializerOptions instance.
+ internal override void Initialize(JsonSerializerOptions options)
+ {
+ JsonNamingPolicy? namingPolicy = options.PropertyNamingPolicy;
+
+ if (namingPolicy == null)
+ {
+ _keyName = KeyNameCLR;
+ _valueName = ValueNameCLR;
+ }
+ else
+ {
+ _keyName = namingPolicy.ConvertName(KeyNameCLR);
+ _valueName = namingPolicy.ConvertName(ValueNameCLR);
+
+ if (_keyName == null || _valueName == null)
+ {
+ ThrowHelper.ThrowInvalidOperationException_NamingPolicyReturnNull(namingPolicy);
+ }
+ }
+
+ JavaScriptEncoder? encoder = options.Encoder;
+ _keyNameEncoded = JsonEncodedText.Encode(_keyName, encoder);
+ _valueNameEncoded = JsonEncodedText.Encode(_valueName, encoder);
+ }
+
internal override bool OnTryRead(
ref Utf8JsonReader reader,
Type typeToConvert, JsonSerializerOptions options,
@@ -44,17 +73,19 @@ internal override bool OnTryRead(
ThrowHelper.ThrowJsonException();
}
+ bool caseInsensitiveMatch = options.PropertyNameCaseInsensitive;
+
string propertyName = reader.GetString()!;
- if (propertyName == KeyName)
+ if (FoundKeyProperty(propertyName, caseInsensitiveMatch))
{
reader.ReadWithVerify();
- k = JsonSerializer.Deserialize(ref reader, options, ref state, KeyName);
+ k = JsonSerializer.Deserialize(ref reader, options, ref state, _keyName);
keySet = true;
}
- else if (propertyName == ValueName)
+ else if (FoundValueProperty(propertyName, caseInsensitiveMatch))
{
reader.ReadWithVerify();
- v = JsonSerializer.Deserialize(ref reader, options, ref state, ValueName);
+ v = JsonSerializer.Deserialize(ref reader, options, ref state, _valueName);
valueSet = true;
}
else
@@ -70,28 +101,21 @@ internal override bool OnTryRead(
}
propertyName = reader.GetString()!;
- if (propertyName == KeyName)
+ if (!keySet && FoundKeyProperty(propertyName, caseInsensitiveMatch))
{
reader.ReadWithVerify();
- k = JsonSerializer.Deserialize(ref reader, options, ref state, KeyName);
- keySet = true;
+ k = JsonSerializer.Deserialize(ref reader, options, ref state, _keyName);
}
- else if (propertyName == ValueName)
+ else if (!valueSet && FoundValueProperty(propertyName, caseInsensitiveMatch))
{
reader.ReadWithVerify();
- v = JsonSerializer.Deserialize(ref reader, options, ref state, ValueName);
- valueSet = true;
+ v = JsonSerializer.Deserialize(ref reader, options, ref state, _valueName);
}
else
{
ThrowHelper.ThrowJsonException();
}
- if (!keySet || !valueSet)
- {
- ThrowHelper.ThrowJsonException();
- }
-
reader.ReadWithVerify();
if (reader.TokenType != JsonTokenType.EndObject)
@@ -107,14 +131,28 @@ internal override bool OnTryWrite(Utf8JsonWriter writer, KeyValuePair false;
internal ConstructorInfo? ConstructorInfo { get; set; }
+
+ internal virtual void Initialize(JsonSerializerOptions options) { }
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs
index 8d0bb4d01b4033..0d7bd68400ee44 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs
@@ -163,9 +163,9 @@ public static void ThrowInvalidOperationException_SerializerPropertyNameNull(Typ
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
- public static void ThrowInvalidOperationException_SerializerDictionaryKeyNull(Type policyType)
+ public static void ThrowInvalidOperationException_NamingPolicyReturnNull(JsonNamingPolicy namingPolicy)
{
- throw new InvalidOperationException(SR.Format(SR.SerializerDictionaryKeyNull, policyType));
+ throw new InvalidOperationException(SR.Format(SR.NamingPolicyReturnNull, namingPolicy));
}
[DoesNotReturn]
diff --git a/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Generic.Read.cs b/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Generic.Read.cs
index d1cb10248613af..3e9eae828e837e 100644
--- a/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Generic.Read.cs
+++ b/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Generic.Read.cs
@@ -962,167 +962,6 @@ public static void ReadSimpleSortedSetT()
Assert.Equal(0, result.Count());
}
- [Fact]
- public static void ReadSimpleKeyValuePairFail()
- {
- // Invalid form: no Value
- Assert.Throws(() => JsonSerializer.Deserialize>(@"{""Key"": 123}"));
-
- // Invalid form: extra property
- Assert.Throws(() => JsonSerializer.Deserialize>(@"{""Key"": ""Key"", ""Value"": 123, ""Value2"": 456}"));
-
- // Invalid form: does not contain both Key and Value properties
- Assert.Throws(() => JsonSerializer.Deserialize>(@"{""Key"": ""Key"", ""Val"": 123"));
- }
-
- [Fact]
- public static void ReadListOfKeyValuePair()
- {
- List> input = JsonSerializer.Deserialize>>(@"[{""Key"": ""123"", ""Value"": 123},{""Key"": ""456"", ""Value"": 456}]");
-
- Assert.Equal(2, input.Count);
- Assert.Equal("123", input[0].Key);
- Assert.Equal(123, input[0].Value);
- Assert.Equal("456", input[1].Key);
- Assert.Equal(456, input[1].Value);
- }
-
- [Fact]
- public static void ReadKeyValuePairOfList()
- {
- KeyValuePair> input = JsonSerializer.Deserialize>>(@"{""Key"":""Key"", ""Value"":[1, 2, 3]}");
-
- Assert.Equal("Key", input.Key);
- Assert.Equal(3, input.Value.Count);
- Assert.Equal(1, input.Value[0]);
- Assert.Equal(2, input.Value[1]);
- Assert.Equal(3, input.Value[2]);
- }
-
- [Theory]
- [InlineData(@"{""Key"":""Key"", ""Value"":{""Key"":1, ""Value"":2}}")]
- [InlineData(@"{""Key"":""Key"", ""Value"":{""Value"":2, ""Key"":1}}")]
- [InlineData(@"{""Value"":{""Key"":1, ""Value"":2}, ""Key"":""Key""}")]
- [InlineData(@"{""Value"":{""Value"":2, ""Key"":1}, ""Key"":""Key""}")]
- public static void ReadKeyValuePairOfKeyValuePair(string json)
- {
- KeyValuePair> input = JsonSerializer.Deserialize>>(json);
-
- Assert.Equal("Key", input.Key);
- Assert.Equal(1, input.Value.Key);
- Assert.Equal(2, input.Value.Value);
- }
-
- [Fact]
- public static void ReadKeyValuePairWithNullValues()
- {
- {
- KeyValuePair kvp = JsonSerializer.Deserialize>(@"{""Key"":""key"",""Value"":null}");
- Assert.Equal("key", kvp.Key);
- Assert.Null(kvp.Value);
- }
-
- {
- KeyValuePair kvp = JsonSerializer.Deserialize>(@"{""Key"":""key"",""Value"":null}");
- Assert.Equal("key", kvp.Key);
- Assert.Null(kvp.Value);
- }
-
- {
- KeyValuePair kvp = JsonSerializer.Deserialize>(@"{""Key"":""key"",""Value"":null}");
- Assert.Equal("key", kvp.Key);
- Assert.Null(kvp.Value);
- }
-
- {
- KeyValuePair> kvp = JsonSerializer.Deserialize>>(@"{""Key"":""key"",""Value"":{""Key"":""key"",""Value"":null}}");
- Assert.Equal("key", kvp.Key);
- Assert.Equal("key", kvp.Value.Key);
- Assert.Null(kvp.Value.Value);
- }
-
- {
- KeyValuePair> kvp = JsonSerializer.Deserialize>>(@"{""Key"":""key"",""Value"":{""Key"":""key"",""Value"":null}}");
- Assert.Equal("key", kvp.Key);
- Assert.Equal("key", kvp.Value.Key);
- Assert.Null(kvp.Value.Value);
- }
-
- {
- KeyValuePair> kvp = JsonSerializer.Deserialize>>(@"{""Key"":""key"",""Value"":{""Key"":""key"",""Value"":null}}");
- Assert.Equal("key", kvp.Key);
- Assert.Equal("key", kvp.Value.Key);
- Assert.Null(kvp.Value.Value);
- }
- }
-
- [Fact]
- public static void ReadClassWithNullKeyValuePairValues()
- {
- string json =
- @"{" +
- @"""KvpWStrVal"":{" +
- @"""Key"":""key""," +
- @"""Value"":null" +
- @"}," +
- @"""KvpWObjVal"":{" +
- @"""Key"":""key""," +
- @"""Value"":null" +
- @"}," +
- @"""KvpWClassVal"":{" +
- @"""Key"":""key""," +
- @"""Value"":null" +
- @"}," +
- @"""KvpWStrKvpVal"":{" +
- @"""Key"":""key""," +
- @"""Value"":{" +
- @"""Key"":""key""," +
- @"""Value"":null" +
- @"}" +
- @"}," +
- @"""KvpWObjKvpVal"":{" +
- @"""Key"":""key""," +
- @"""Value"":{" +
- @"""Key"":""key""," +
- @"""Value"":null" +
- @"}" +
- @"}," +
- @"""KvpWClassKvpVal"":{" +
- @"""Key"":""key""," +
- @"""Value"":{" +
- @"""Key"":""key""," +
- @"""Value"":null" +
- @"}" +
- @"}" +
- @"}";
- SimpleClassWithKeyValuePairs obj = JsonSerializer.Deserialize(json);
-
- Assert.Equal("key", obj.KvpWStrVal.Key);
- Assert.Equal("key", obj.KvpWObjVal.Key);
- Assert.Equal("key", obj.KvpWClassVal.Key);
- Assert.Equal("key", obj.KvpWStrKvpVal.Key);
- Assert.Equal("key", obj.KvpWObjKvpVal.Key);
- Assert.Equal("key", obj.KvpWClassKvpVal.Key);
- Assert.Equal("key", obj.KvpWStrKvpVal.Value.Key);
- Assert.Equal("key", obj.KvpWObjKvpVal.Value.Key);
- Assert.Equal("key", obj.KvpWClassKvpVal.Value.Key);
-
- Assert.Null(obj.KvpWStrVal.Value);
- Assert.Null(obj.KvpWObjVal.Value);
- Assert.Null(obj.KvpWClassVal.Value);
- Assert.Null(obj.KvpWStrKvpVal.Value.Value);
- Assert.Null(obj.KvpWObjKvpVal.Value.Value);
- Assert.Null(obj.KvpWClassKvpVal.Value.Value);
- }
-
- [Fact]
- public static void Kvp_NullKeyIsFine()
- {
- KeyValuePair kvp = JsonSerializer.Deserialize>(@"{""Key"":null,""Value"":null}");
- Assert.Null(kvp.Key);
- Assert.Null(kvp.Value);
- }
-
[Fact]
public static void ReadSimpleTestClass_GenericCollectionWrappers()
{
diff --git a/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Generic.Write.cs b/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Generic.Write.cs
index e408f09b0e7fd0..2c2c61819d1ac6 100644
--- a/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Generic.Write.cs
+++ b/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Generic.Write.cs
@@ -750,117 +750,6 @@ public static void WritePrimitiveSortedSetT()
Assert.Equal("[1,2]", json);
}
- [Fact]
- public static void WritePrimitiveKeyValuePair()
- {
- KeyValuePair input = new KeyValuePair("Key", 123) ;
-
- string json = JsonSerializer.Serialize(input);
- Assert.Equal(@"{""Key"":""Key"",""Value"":123}", json);
- }
-
- [Fact]
- public static void WriteListOfKeyValuePair()
- {
- List> input = new List>
- {
- new KeyValuePair("123", 123),
- new KeyValuePair("456", 456)
- };
-
- string json = JsonSerializer.Serialize(input);
- Assert.Equal(@"[{""Key"":""123"",""Value"":123},{""Key"":""456"",""Value"":456}]", json);
- }
-
- [Fact]
- public static void WriteKeyValuePairOfList()
- {
- KeyValuePair> input = new KeyValuePair>("Key", new List { 1, 2, 3 });
-
- string json = JsonSerializer.Serialize(input);
- Assert.Equal(@"{""Key"":""Key"",""Value"":[1,2,3]}", json);
- }
-
- [Fact]
- public static void WriteKeyValuePairOfKeyValuePair()
- {
- KeyValuePair> input = new KeyValuePair>(
- "Key", new KeyValuePair("Key", 1));
-
- string json = JsonSerializer.Serialize(input);
- Assert.Equal(@"{""Key"":""Key"",""Value"":{""Key"":""Key"",""Value"":1}}", json);
- }
-
- [Fact]
- public static void WriteKeyValuePairWithNullValues()
- {
- {
- KeyValuePair kvp = new KeyValuePair("key", null);
- Assert.Equal(@"{""Key"":""key"",""Value"":null}", JsonSerializer.Serialize(kvp));
- }
-
- {
- KeyValuePair kvp = new KeyValuePair("key", null);
- Assert.Equal(@"{""Key"":""key"",""Value"":null}", JsonSerializer.Serialize(kvp));
- }
-
- {
- KeyValuePair kvp = new KeyValuePair("key", null);
- Assert.Equal(@"{""Key"":""key"",""Value"":null}", JsonSerializer.Serialize(kvp));
- }
-
- {
- KeyValuePair> kvp = new KeyValuePair>("key", new KeyValuePair("key", null));
- Assert.Equal(@"{""Key"":""key"",""Value"":{""Key"":""key"",""Value"":null}}", JsonSerializer.Serialize(kvp));
- }
-
- {
- KeyValuePair> kvp = new KeyValuePair>("key", new KeyValuePair("key", null));
- Assert.Equal(@"{""Key"":""key"",""Value"":{""Key"":""key"",""Value"":null}}", JsonSerializer.Serialize(kvp));
- }
-
- {
- KeyValuePair> kvp = new KeyValuePair>("key", new KeyValuePair("key", null));
- Assert.Equal(@"{""Key"":""key"",""Value"":{""Key"":""key"",""Value"":null}}", JsonSerializer.Serialize(kvp));
- }
- }
-
- // https://github.com/dotnet/runtime/issues/30388
- [Fact]
- public static void WriteClassWithNullKeyValuePairValues_NullWrittenAsEmptyObject()
- {
- var value = new SimpleClassWithKeyValuePairs()
- {
- KvpWStrVal = new KeyValuePair("key", null),
- KvpWObjVal = new KeyValuePair("key", null),
- KvpWClassVal = new KeyValuePair("key", null),
- KvpWStrKvpVal = new KeyValuePair>("key", new KeyValuePair("key", null)),
- KvpWObjKvpVal = new KeyValuePair>("key", new KeyValuePair("key", null)),
- KvpWClassKvpVal = new KeyValuePair>("key", new KeyValuePair("key", null)),
- };
-
- string result = JsonSerializer.Serialize(value);
-
- // Roundtrip to ensure serialize was correct.
- value = JsonSerializer.Deserialize(result);
- Assert.Equal("key", value.KvpWStrVal.Key);
- Assert.Equal("key", value.KvpWObjVal.Key);
- Assert.Equal("key", value.KvpWClassVal.Key);
- Assert.Equal("key", value.KvpWStrKvpVal.Key);
- Assert.Equal("key", value.KvpWObjKvpVal.Key);
- Assert.Equal("key", value.KvpWClassKvpVal.Key);
- Assert.Equal("key", value.KvpWStrKvpVal.Value.Key);
- Assert.Equal("key", value.KvpWObjKvpVal.Value.Key);
- Assert.Equal("key", value.KvpWClassKvpVal.Value.Key);
-
- Assert.Null(value.KvpWStrVal.Value);
- Assert.Null(value.KvpWObjVal.Value);
- Assert.Null(value.KvpWClassVal.Value);
- Assert.Null(value.KvpWStrKvpVal.Value.Value);
- Assert.Null(value.KvpWObjKvpVal.Value.Value);
- Assert.Null(value.KvpWClassKvpVal.Value.Value);
- }
-
[Fact]
public static void WriteGenericCollectionWrappers()
{
diff --git a/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.KeyValuePair.cs b/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.KeyValuePair.cs
new file mode 100644
index 00000000000000..868167086cfa9c
--- /dev/null
+++ b/src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.KeyValuePair.cs
@@ -0,0 +1,438 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Text.Encodings.Web;
+using Xunit;
+
+namespace System.Text.Json.Serialization.Tests
+{
+ public static partial class CollectionTests
+ {
+ [Fact]
+ public static void ReadSimpleKeyValuePairFail()
+ {
+ // Invalid form: no Value
+ Assert.Throws(() => JsonSerializer.Deserialize>(@"{""Key"": 123}"));
+
+ // Invalid form: extra property
+ Assert.Throws(() => JsonSerializer.Deserialize>(@"{""Key"": ""Key"", ""Value"": 123, ""Value2"": 456}"));
+
+ // Invalid form: does not contain both Key and Value properties
+ Assert.Throws(() => JsonSerializer.Deserialize>(@"{""Key"": ""Key"", ""Val"": 123"));
+ }
+
+ [Fact]
+ public static void ReadListOfKeyValuePair()
+ {
+ List> input = JsonSerializer.Deserialize>>(@"[{""Key"": ""123"", ""Value"": 123},{""Key"": ""456"", ""Value"": 456}]");
+
+ Assert.Equal(2, input.Count);
+ Assert.Equal("123", input[0].Key);
+ Assert.Equal(123, input[0].Value);
+ Assert.Equal("456", input[1].Key);
+ Assert.Equal(456, input[1].Value);
+ }
+
+ [Fact]
+ public static void ReadKeyValuePairOfList()
+ {
+ KeyValuePair> input = JsonSerializer.Deserialize>>(@"{""Key"":""Key"", ""Value"":[1, 2, 3]}");
+
+ Assert.Equal("Key", input.Key);
+ Assert.Equal(3, input.Value.Count);
+ Assert.Equal(1, input.Value[0]);
+ Assert.Equal(2, input.Value[1]);
+ Assert.Equal(3, input.Value[2]);
+ }
+
+ [Theory]
+ [InlineData(@"{""Key"":""Key"", ""Value"":{""Key"":1, ""Value"":2}}")]
+ [InlineData(@"{""Key"":""Key"", ""Value"":{""Value"":2, ""Key"":1}}")]
+ [InlineData(@"{""Value"":{""Key"":1, ""Value"":2}, ""Key"":""Key""}")]
+ [InlineData(@"{""Value"":{""Value"":2, ""Key"":1}, ""Key"":""Key""}")]
+ public static void ReadKeyValuePairOfKeyValuePair(string json)
+ {
+ KeyValuePair> input = JsonSerializer.Deserialize>>(json);
+
+ Assert.Equal("Key", input.Key);
+ Assert.Equal(1, input.Value.Key);
+ Assert.Equal(2, input.Value.Value);
+ }
+
+ [Fact]
+ public static void ReadKeyValuePairWithNullValues()
+ {
+ {
+ KeyValuePair kvp = JsonSerializer.Deserialize>(@"{""Key"":""key"",""Value"":null}");
+ Assert.Equal("key", kvp.Key);
+ Assert.Null(kvp.Value);
+ }
+
+ {
+ KeyValuePair kvp = JsonSerializer.Deserialize>(@"{""Key"":""key"",""Value"":null}");
+ Assert.Equal("key", kvp.Key);
+ Assert.Null(kvp.Value);
+ }
+
+ {
+ KeyValuePair kvp = JsonSerializer.Deserialize>(@"{""Key"":""key"",""Value"":null}");
+ Assert.Equal("key", kvp.Key);
+ Assert.Null(kvp.Value);
+ }
+
+ {
+ KeyValuePair> kvp = JsonSerializer.Deserialize>>(@"{""Key"":""key"",""Value"":{""Key"":""key"",""Value"":null}}");
+ Assert.Equal("key", kvp.Key);
+ Assert.Equal("key", kvp.Value.Key);
+ Assert.Null(kvp.Value.Value);
+ }
+
+ {
+ KeyValuePair> kvp = JsonSerializer.Deserialize>>(@"{""Key"":""key"",""Value"":{""Key"":""key"",""Value"":null}}");
+ Assert.Equal("key", kvp.Key);
+ Assert.Equal("key", kvp.Value.Key);
+ Assert.Null(kvp.Value.Value);
+ }
+
+ {
+ KeyValuePair> kvp = JsonSerializer.Deserialize>>(@"{""Key"":""key"",""Value"":{""Key"":""key"",""Value"":null}}");
+ Assert.Equal("key", kvp.Key);
+ Assert.Equal("key", kvp.Value.Key);
+ Assert.Null(kvp.Value.Value);
+ }
+ }
+
+ [Fact]
+ public static void ReadClassWithNullKeyValuePairValues()
+ {
+ string json =
+ @"{" +
+ @"""KvpWStrVal"":{" +
+ @"""Key"":""key""," +
+ @"""Value"":null" +
+ @"}," +
+ @"""KvpWObjVal"":{" +
+ @"""Key"":""key""," +
+ @"""Value"":null" +
+ @"}," +
+ @"""KvpWClassVal"":{" +
+ @"""Key"":""key""," +
+ @"""Value"":null" +
+ @"}," +
+ @"""KvpWStrKvpVal"":{" +
+ @"""Key"":""key""," +
+ @"""Value"":{" +
+ @"""Key"":""key""," +
+ @"""Value"":null" +
+ @"}" +
+ @"}," +
+ @"""KvpWObjKvpVal"":{" +
+ @"""Key"":""key""," +
+ @"""Value"":{" +
+ @"""Key"":""key""," +
+ @"""Value"":null" +
+ @"}" +
+ @"}," +
+ @"""KvpWClassKvpVal"":{" +
+ @"""Key"":""key""," +
+ @"""Value"":{" +
+ @"""Key"":""key""," +
+ @"""Value"":null" +
+ @"}" +
+ @"}" +
+ @"}";
+ SimpleClassWithKeyValuePairs obj = JsonSerializer.Deserialize(json);
+
+ Assert.Equal("key", obj.KvpWStrVal.Key);
+ Assert.Equal("key", obj.KvpWObjVal.Key);
+ Assert.Equal("key", obj.KvpWClassVal.Key);
+ Assert.Equal("key", obj.KvpWStrKvpVal.Key);
+ Assert.Equal("key", obj.KvpWObjKvpVal.Key);
+ Assert.Equal("key", obj.KvpWClassKvpVal.Key);
+ Assert.Equal("key", obj.KvpWStrKvpVal.Value.Key);
+ Assert.Equal("key", obj.KvpWObjKvpVal.Value.Key);
+ Assert.Equal("key", obj.KvpWClassKvpVal.Value.Key);
+
+ Assert.Null(obj.KvpWStrVal.Value);
+ Assert.Null(obj.KvpWObjVal.Value);
+ Assert.Null(obj.KvpWClassVal.Value);
+ Assert.Null(obj.KvpWStrKvpVal.Value.Value);
+ Assert.Null(obj.KvpWObjKvpVal.Value.Value);
+ Assert.Null(obj.KvpWClassKvpVal.Value.Value);
+ }
+
+ [Fact]
+ public static void Kvp_NullKeyIsFine()
+ {
+ KeyValuePair kvp = JsonSerializer.Deserialize>(@"{""Key"":null,""Value"":null}");
+ Assert.Null(kvp.Key);
+ Assert.Null(kvp.Value);
+ }
+
+ [Fact]
+ public static void WritePrimitiveKeyValuePair()
+ {
+ KeyValuePair input = new KeyValuePair("Key", 123);
+
+ string json = JsonSerializer.Serialize(input);
+ Assert.Equal(@"{""Key"":""Key"",""Value"":123}", json);
+ }
+
+ [Fact]
+ public static void WriteListOfKeyValuePair()
+ {
+ List> input = new List>
+ {
+ new KeyValuePair("123", 123),
+ new KeyValuePair("456", 456)
+ };
+
+ string json = JsonSerializer.Serialize(input);
+ Assert.Equal(@"[{""Key"":""123"",""Value"":123},{""Key"":""456"",""Value"":456}]", json);
+ }
+
+ [Fact]
+ public static void WriteKeyValuePairOfList()
+ {
+ KeyValuePair> input = new KeyValuePair>("Key", new List { 1, 2, 3 });
+
+ string json = JsonSerializer.Serialize(input);
+ Assert.Equal(@"{""Key"":""Key"",""Value"":[1,2,3]}", json);
+ }
+
+ [Fact]
+ public static void WriteKeyValuePairOfKeyValuePair()
+ {
+ KeyValuePair> input = new KeyValuePair>(
+ "Key", new KeyValuePair("Key", 1));
+
+ string json = JsonSerializer.Serialize(input);
+ Assert.Equal(@"{""Key"":""Key"",""Value"":{""Key"":""Key"",""Value"":1}}", json);
+ }
+
+ [Fact]
+ public static void WriteKeyValuePairWithNullValues()
+ {
+ {
+ KeyValuePair kvp = new KeyValuePair("key", null);
+ Assert.Equal(@"{""Key"":""key"",""Value"":null}", JsonSerializer.Serialize(kvp));
+ }
+
+ {
+ KeyValuePair kvp = new KeyValuePair("key", null);
+ Assert.Equal(@"{""Key"":""key"",""Value"":null}", JsonSerializer.Serialize(kvp));
+ }
+
+ {
+ KeyValuePair kvp = new KeyValuePair("key", null);
+ Assert.Equal(@"{""Key"":""key"",""Value"":null}", JsonSerializer.Serialize(kvp));
+ }
+
+ {
+ KeyValuePair> kvp = new KeyValuePair>("key", new KeyValuePair("key", null));
+ Assert.Equal(@"{""Key"":""key"",""Value"":{""Key"":""key"",""Value"":null}}", JsonSerializer.Serialize(kvp));
+ }
+
+ {
+ KeyValuePair> kvp = new KeyValuePair>("key", new KeyValuePair("key", null));
+ Assert.Equal(@"{""Key"":""key"",""Value"":{""Key"":""key"",""Value"":null}}", JsonSerializer.Serialize(kvp));
+ }
+
+ {
+ KeyValuePair> kvp = new KeyValuePair>("key", new KeyValuePair("key", null));
+ Assert.Equal(@"{""Key"":""key"",""Value"":{""Key"":""key"",""Value"":null}}", JsonSerializer.Serialize(kvp));
+ }
+ }
+
+ [Fact]
+ public static void WriteClassWithNullKeyValuePairValues_NullWrittenAsEmptyObject()
+ {
+ var value = new SimpleClassWithKeyValuePairs()
+ {
+ KvpWStrVal = new KeyValuePair("key", null),
+ KvpWObjVal = new KeyValuePair("key", null),
+ KvpWClassVal = new KeyValuePair("key", null),
+ KvpWStrKvpVal = new KeyValuePair>("key", new KeyValuePair("key", null)),
+ KvpWObjKvpVal = new KeyValuePair>("key", new KeyValuePair("key", null)),
+ KvpWClassKvpVal = new KeyValuePair>("key", new KeyValuePair("key", null)),
+ };
+
+ string result = JsonSerializer.Serialize(value);
+
+ // Roundtrip to ensure serialize was correct.
+ value = JsonSerializer.Deserialize(result);
+ Assert.Equal("key", value.KvpWStrVal.Key);
+ Assert.Equal("key", value.KvpWObjVal.Key);
+ Assert.Equal("key", value.KvpWClassVal.Key);
+ Assert.Equal("key", value.KvpWStrKvpVal.Key);
+ Assert.Equal("key", value.KvpWObjKvpVal.Key);
+ Assert.Equal("key", value.KvpWClassKvpVal.Key);
+ Assert.Equal("key", value.KvpWStrKvpVal.Value.Key);
+ Assert.Equal("key", value.KvpWObjKvpVal.Value.Key);
+ Assert.Equal("key", value.KvpWClassKvpVal.Value.Key);
+
+ Assert.Null(value.KvpWStrVal.Value);
+ Assert.Null(value.KvpWObjVal.Value);
+ Assert.Null(value.KvpWClassVal.Value);
+ Assert.Null(value.KvpWStrKvpVal.Value.Value);
+ Assert.Null(value.KvpWObjKvpVal.Value.Value);
+ Assert.Null(value.KvpWClassKvpVal.Value.Value);
+ }
+
+ [Fact]
+ public static void HonorNamingPolicy()
+ {
+ var kvp = new KeyValuePair("Hello, World!", 1);
+
+ var options = new JsonSerializerOptions
+ {
+ PropertyNamingPolicy = new LeadingUnderscorePolicy()
+ };
+
+ string serialized = JsonSerializer.Serialize(kvp, options);
+ // We know serializer writes the key first.
+ Assert.Equal(@"{""_Key"":""Hello, World!"",""_Value"":1}", serialized);
+
+ kvp = JsonSerializer.Deserialize>(serialized, options);
+ Assert.Equal("Hello, World!", kvp.Key);
+ Assert.Equal(1, kvp.Value);
+ }
+
+ [Fact]
+ public static void HonorNamingPolicy_CaseInsensitive()
+ {
+ const string json = @"{""key"":""Hello, World!"",""value"":1}";
+
+ // Baseline - with case-sensitive matching, the payload doesn't have mapping properties.
+ Assert.Throws(() => JsonSerializer.Deserialize>(json));
+
+ // Test - with case-insensitivity on, we have property matches.
+ var options = new JsonSerializerOptions
+ {
+ PropertyNameCaseInsensitive = true
+ };
+
+ KeyValuePair kvp = JsonSerializer.Deserialize>(json, options);
+ Assert.Equal("Hello, World!", kvp.Key);
+ Assert.Equal(1, kvp.Value);
+ }
+
+ [Fact]
+ public static void HonorCLRProperties()
+ {
+ var options = new JsonSerializerOptions
+ {
+ PropertyNamingPolicy = new LeadingUnderscorePolicy() // Key -> _Key, Value -> _Value
+ };
+
+ // Although policy won't produce this JSON string, the serializer parses the properties
+ // as "Key" and "Value" are special cased to accomodate content serialized with previous
+ // versions of the serializer (.NET Core 3.x/System.Text.Json 4.7.x).
+ string json = @"{""Key"":""Hello, World!"",""Value"":1}";
+ KeyValuePair kvp = JsonSerializer.Deserialize>(json, options);
+ Assert.Equal("Hello, World!", kvp.Key);
+ Assert.Equal(1, kvp.Value);
+
+ // "Key" and "Value" matching is case sensitive.
+ json = @"{""key"":""Hello, World!"",""value"":1}";
+ Assert.Throws(() => JsonSerializer.Deserialize>(json, options));
+
+ // "Key" and "Value" matching is case sensitive, even when case sensitivity is on.
+ // Case sensitivity only applies to the result of converting the CLR property names
+ // (Key -> _Key, Value -> _Value) with the naming policy.
+ options = new JsonSerializerOptions
+ {
+ PropertyNamingPolicy = new LeadingUnderscorePolicy(),
+ PropertyNameCaseInsensitive = true
+ };
+
+ Assert.Throws(() => JsonSerializer.Deserialize>(json, options));
+ }
+
+ private class LeadingUnderscorePolicy : JsonNamingPolicy
+ {
+ public override string ConvertName(string name) => "_" + name;
+ }
+
+ [Fact]
+ public static void HonorCustomEncoder()
+ {
+ var kvp = new KeyValuePair(1, 2);
+
+ JsonNamingPolicy namingPolicy = new TrailingAngleBracketPolicy();
+
+ // Baseline - properties serialized with default encoder if none specified.
+ var options = new JsonSerializerOptions
+ {
+ PropertyNamingPolicy = namingPolicy,
+ };
+
+ Assert.Equal(@"{""Key\u003C"":1,""Value\u003C"":2}", JsonSerializer.Serialize(kvp, options));
+
+ // Test - serializer honors custom encoder.
+ options = new JsonSerializerOptions
+ {
+ PropertyNamingPolicy = namingPolicy,
+ Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
+ };
+
+ Assert.Equal(@"{""Key<"":1,""Value<"":2}", JsonSerializer.Serialize(kvp, options));
+ }
+
+ private class TrailingAngleBracketPolicy : JsonNamingPolicy
+ {
+ public override string ConvertName(string name) => name + "<";
+ }
+
+ [Theory]
+ [InlineData(typeof(KeyNameNullPolicy))]
+ [InlineData(typeof(ValueNameNullPolicy))]
+ public static void InvalidPropertyNameFail(Type policyType)
+ {
+ var options = new JsonSerializerOptions
+ {
+ PropertyNamingPolicy = (JsonNamingPolicy)Activator.CreateInstance(policyType)
+ };
+
+ InvalidOperationException ex = Assert.Throws(() => JsonSerializer.Deserialize>("", options));
+ string exAsStr = ex.ToString();
+ Assert.Contains(policyType.ToString(), exAsStr);
+
+ Assert.Throws(() => JsonSerializer.Serialize(new KeyValuePair("", ""), options));
+ }
+
+ private class KeyNameNullPolicy : JsonNamingPolicy
+ {
+ public override string ConvertName(string name) => name == "Key" ? null : name;
+ }
+
+ private class ValueNameNullPolicy : JsonNamingPolicy
+ {
+ public override string ConvertName(string name) => name == "Value" ? null : name;
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData("1")]
+ [InlineData("[")]
+ [InlineData("}")]
+ [InlineData("{")]
+ [InlineData("{}")]
+ [InlineData("{Key")]
+ [InlineData("{0")]
+ [InlineData(@"{""Random"":")]
+ [InlineData(@"{""Value"":1}")]
+ [InlineData(@"{""Value"":1,2")]
+ [InlineData(@"{""Value"":1,""Random"":")]
+ [InlineData(@"{""Key"":1,""Key"":1}")]
+ [InlineData(@"{""Key"":1,""Key"":2}")]
+ [InlineData(@"{""Value"":1,""Value"":1}")]
+ [InlineData(@"{""Value"":1,""Value"":2}")]
+ public static void InvalidJsonFail(string json)
+ {
+ Assert.Throws(() => JsonSerializer.Deserialize>(json));
+ }
+ }
+}
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj
index c55cc68b8500d1..2693078629bfcb 100644
--- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj
+++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj
@@ -1,4 +1,4 @@
-
+
$(NetCoreAppCurrent);$(NetFrameworkCurrent)
true
@@ -8,8 +8,7 @@
$(DefineConstants);BUILDING_INBOX_LIBRARY
-
+
@@ -45,6 +44,7 @@
+
@@ -137,12 +137,10 @@
-
+
-
+