<NoWarn Condition="'$(TargetFramework)' == 'netstandard2.0' or $(TargetFramework.StartsWith('net4'))">$(NoWarn);nullable</NoWarn>
</PropertyGroup>
<ItemGroup>
- <Compile Include="$(CommonPath)System\Runtime\CompilerServices\PreserveDependencyAttribute.cs"
- Link="Common\System\Runtime\CompilerServices\PreserveDependencyAttribute.cs" />
- <Compile Include="$(CommonPath)System\HexConverter.cs"
- Link="Common\System\HexConverter.cs" />
+ <Compile Include="$(CommonPath)System\Runtime\CompilerServices\PreserveDependencyAttribute.cs" Link="Common\System\Runtime\CompilerServices\PreserveDependencyAttribute.cs" />
+ <Compile Include="$(CommonPath)System\HexConverter.cs" Link="Common\System\HexConverter.cs" />
<Compile Include="System\Text\Json\BitStack.cs" />
<Compile Include="System\Text\Json\Document\JsonDocument.cs" />
<Compile Include="System\Text\Json\Document\JsonDocument.DbRow.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializerOptions.Converters.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializerOptions.cs" />
<Compile Include="System\Text\Json\Serialization\JsonStringEnumConverter.cs" />
- <Compile Include="System\Text\Json\Serialization\JsonValueConverterOfT.cs" />
<Compile Include="System\Text\Json\Serialization\MemberAccessor.cs" />
<Compile Include="System\Text\Json\Serialization\MetadataPropertyName.cs" />
<Compile Include="System\Text\Json\Serialization\ParameterRef.cs" />
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0' or $(TargetFramework.StartsWith('net4'))">
<Compile Include="System\Collections\Generic\StackExtensions.netstandard.cs" />
<!-- Common or Common-branched source files -->
- <Compile Include="$(CommonPath)System\Buffers\ArrayBufferWriter.cs"
- Link="Common\System\Buffers\ArrayBufferWriter.cs" />
+ <Compile Include="$(CommonPath)System\Buffers\ArrayBufferWriter.cs" Link="Common\System\Buffers\ArrayBufferWriter.cs" />
<Reference Include="mscorlib" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="Microsoft.Bcl.AsyncInterfaces" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0' or $(TargetFramework.StartsWith('net4')) or '$(TargetFramework)' == 'netcoreapp3.0'">
- <Compile Include="$(CommonPath)System\Collections\Generic\ReferenceEqualityComparer.cs"
- Link="Common\System\Collections\Generic\ReferenceEqualityComparer.cs" />
+ <Compile Include="$(CommonPath)System\Collections\Generic\ReferenceEqualityComparer.cs" Link="Common\System\Collections\Generic\ReferenceEqualityComparer.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == '$(NetCoreAppCurrent)' or '$(TargetFramework)' == 'netcoreapp3.0'">
<Reference Include="System.Collections" />
// For performance, we order the parameters by the first deserialize and PropertyIndex helps find the right slot quicker.
public int ParameterIndex;
public List<ParameterRef>? ParameterRefCache;
+
+ // Used when deserializing KeyValuePair instances.
+ public bool FoundKey;
+ public bool FoundValue;
}
}
/// Implementation of <cref>JsonObjectConverter{T}</cref> that supports the deserialization
/// of JSON objects using parameterized constructors.
/// </summary>
- internal sealed class SmallObjectWithParameterizedConstructorConverter<T, TArg0, TArg1, TArg2, TArg3> : ObjectWithParameterizedConstructorConverter<T> where T : notnull
+ internal class SmallObjectWithParameterizedConstructorConverter<T, TArg0, TArg1, TArg2, TArg3> : ObjectWithParameterizedConstructorConverter<T> where T : notnull
{
protected override object CreateObject(ref ReadStackFrame frame)
{
state.Current.JsonClassInfo.UpdateSortedParameterCache(ref state.Current);
}
+ EndRead(ref state);
+
value = (T)obj;
return true;
InitializeConstructorArgumentCaches(ref state, options);
}
+ protected virtual void EndRead(ref ReadStack state) { }
+
/// <summary>
/// Lookup the constructor parameter given its name in the reader.
/// </summary>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private bool TryLookupConstructorParameter(
+ protected virtual bool TryLookupConstructorParameter(
ref ReadStack state,
ref Utf8JsonReader reader,
JsonSerializerOptions options,
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
-using System.Text.Encodings.Web;
+using System.Diagnostics;
+using System.Reflection;
namespace System.Text.Json.Serialization.Converters
{
- internal sealed class KeyValuePairConverter<TKey, TValue> : JsonValueConverter<KeyValuePair<TKey, TValue>>
+ internal sealed class KeyValuePairConverter<TKey, TValue> :
+ SmallObjectWithParameterizedConstructorConverter<KeyValuePair<TKey, TValue>, TKey, TValue, object, object>
{
private const string KeyNameCLR = "Key";
private const string ValueNameCLR = "Value";
+ private const int NumProperties = 2;
+
// 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.
+ private static readonly ConstructorInfo s_constructorInfo =
+ typeof(KeyValuePair<TKey, TValue>).GetConstructor(new[] { typeof(TKey), typeof(TValue) })!;
internal override void Initialize(JsonSerializerOptions options)
{
JsonNamingPolicy? namingPolicy = options.PropertyNamingPolicy;
-
if (namingPolicy == null)
{
_keyName = KeyNameCLR;
_keyName = namingPolicy.ConvertName(KeyNameCLR);
_valueName = namingPolicy.ConvertName(ValueNameCLR);
- if (_keyName == null || _valueName == null)
- {
- ThrowHelper.ThrowInvalidOperationException_NamingPolicyReturnNull(namingPolicy);
- }
+ // Validation for the naming policy will occur during JsonPropertyInfo creation.
}
- JavaScriptEncoder? encoder = options.Encoder;
- _keyNameEncoded = JsonEncodedText.Encode(_keyName, encoder);
- _valueNameEncoded = JsonEncodedText.Encode(_valueName, encoder);
+ ConstructorInfo = s_constructorInfo;
+ Debug.Assert(ConstructorInfo != null);
}
- internal override bool OnTryRead(
- ref Utf8JsonReader reader,
- Type typeToConvert, JsonSerializerOptions options,
+ /// <summary>
+ /// Lookup the constructor parameter given its name in the reader.
+ /// </summary>
+ protected override bool TryLookupConstructorParameter(
ref ReadStack state,
- out KeyValuePair<TKey, TValue> value)
+ ref Utf8JsonReader reader,
+ JsonSerializerOptions options,
+ out JsonParameterInfo? jsonParameterInfo)
{
- if (reader.TokenType != JsonTokenType.StartObject)
- {
- ThrowHelper.ThrowJsonException();
- }
-
- TKey k = default!;
- bool keySet = false;
+ JsonClassInfo classInfo = state.Current.JsonClassInfo;
+ ArgumentState? argState = state.Current.CtorArgumentState;
- TValue v = default!;
- bool valueSet = false;
-
- // Get the first property.
- reader.ReadWithVerify();
- if (reader.TokenType != JsonTokenType.PropertyName)
- {
- ThrowHelper.ThrowJsonException();
- }
+ Debug.Assert(classInfo.ClassType == ClassType.Object);
+ Debug.Assert(argState != null);
+ Debug.Assert(_keyName != null);
+ Debug.Assert(_valueName != null);
bool caseInsensitiveMatch = options.PropertyNameCaseInsensitive;
string propertyName = reader.GetString()!;
- if (FoundKeyProperty(propertyName, caseInsensitiveMatch))
- {
- reader.ReadWithVerify();
- k = JsonSerializer.Deserialize<TKey>(ref reader, options, ref state, _keyName);
- keySet = true;
- }
- else if (FoundValueProperty(propertyName, caseInsensitiveMatch))
- {
- reader.ReadWithVerify();
- v = JsonSerializer.Deserialize<TValue>(ref reader, options, ref state, _valueName);
- valueSet = true;
- }
- else
- {
- ThrowHelper.ThrowJsonException();
- }
+ state.Current.JsonPropertyNameAsString = propertyName;
- // Get the second property.
- reader.ReadWithVerify();
- if (reader.TokenType != JsonTokenType.PropertyName)
+ if (!argState.FoundKey &&
+ FoundKeyProperty(propertyName, caseInsensitiveMatch))
{
- ThrowHelper.ThrowJsonException();
+ jsonParameterInfo = classInfo.ParameterCache![_keyName];
+ argState.FoundKey = true;
}
-
- propertyName = reader.GetString()!;
- if (!keySet && FoundKeyProperty(propertyName, caseInsensitiveMatch))
+ else if (!argState.FoundValue &&
+ FoundValueProperty(propertyName, caseInsensitiveMatch))
{
- reader.ReadWithVerify();
- k = JsonSerializer.Deserialize<TKey>(ref reader, options, ref state, _keyName);
- }
- else if (!valueSet && FoundValueProperty(propertyName, caseInsensitiveMatch))
- {
- reader.ReadWithVerify();
- v = JsonSerializer.Deserialize<TValue>(ref reader, options, ref state, _valueName);
+ jsonParameterInfo = classInfo.ParameterCache![_valueName];
+ argState.FoundValue = true;
}
else
{
ThrowHelper.ThrowJsonException();
+ jsonParameterInfo = null;
+ return false;
}
- reader.ReadWithVerify();
-
- if (reader.TokenType != JsonTokenType.EndObject)
- {
- ThrowHelper.ThrowJsonException();
- }
-
- value = new KeyValuePair<TKey, TValue>(k!, v!);
+ Debug.Assert(jsonParameterInfo != null);
+ argState.ParameterIndex++;
+ argState.JsonParameterInfo = jsonParameterInfo;
return true;
}
- internal override bool OnTryWrite(Utf8JsonWriter writer, KeyValuePair<TKey, TValue> value, JsonSerializerOptions options, ref WriteStack state)
+ protected override void EndRead(ref ReadStack state)
{
- writer.WriteStartObject();
-
- writer.WritePropertyName(_keyNameEncoded);
- JsonSerializer.Serialize(writer, value.Key, options, ref state, _keyName);
-
- writer.WritePropertyName(_valueNameEncoded);
- JsonSerializer.Serialize(writer, value.Value, options, ref state, _valueName);
+ Debug.Assert(state.Current.PropertyIndex == 0);
- writer.WriteEndObject();
- return true;
+ if (state.Current.CtorArgumentState!.ParameterIndex != NumProperties)
+ {
+ ThrowHelper.ThrowJsonException();
+ }
}
private bool FoundKeyProperty(string propertyName, bool caseInsensitiveMatch)
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
+using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
[PreserveDependency(".ctor()", "System.Text.Json.Serialization.Converters.KeyValuePairConverter`2")]
public override JsonConverter CreateConverter(Type type, JsonSerializerOptions options)
{
+ Debug.Assert(CanConvert(type));
+
Type keyType = type.GetGenericArguments()[0];
Type valueType = type.GetGenericArguments()[1];
public static partial class JsonSerializer
{
/// <summary>
- /// Internal version that allows re-entry with preserving ReadStack so that JsonPath works correctly.
- /// </summary>
- [return: MaybeNull]
- internal static TValue Deserialize<TValue>(ref Utf8JsonReader reader, JsonSerializerOptions options, ref ReadStack state, string? propertyName = null)
- {
- if (options == null)
- {
- throw new ArgumentNullException(nameof(options));
- }
-
- state.Current.InitializeReEntry(typeof(TValue), options, propertyName);
-
- JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo!;
-
- JsonConverter<TValue> converter = (JsonConverter<TValue>)jsonPropertyInfo.ConverterBase;
- bool success = converter.TryRead(ref reader, jsonPropertyInfo.RuntimePropertyType!, options, ref state, out TValue value);
- Debug.Assert(success);
-
- // Clear the current property state since we are done processing it.
- state.Current.EndProperty();
-
- return value;
- }
-
- /// <summary>
/// Reads one JSON value (including objects or arrays) from the provided reader into a <typeparamref name="TValue"/>.
/// </summary>
/// <returns>A <typeparamref name="TValue"/> representation of the JSON value.</returns>
public static partial class JsonSerializer
{
/// <summary>
- /// Internal version that allows re-entry with preserving WriteStack so that JsonPath works correctly.
- /// </summary>
- // If this is made public, we will also want to have a non-generic version.
- internal static void Serialize<T>(Utf8JsonWriter writer, T value, JsonSerializerOptions options, ref WriteStack state, string? propertyName = null)
- {
- if (options == null)
- {
- throw new ArgumentNullException(nameof(options));
- }
-
- JsonConverter jsonConverter = state.Current.InitializeReEntry(typeof(T), options, propertyName);
- bool success = jsonConverter.TryWriteAsObject(writer, value, options, ref state);
- Debug.Assert(success);
- }
-
- /// <summary>
/// Write one JSON value (including objects or arrays) to the provided writer.
/// </summary>
/// <param name="writer">The writer to write.</param>
+++ /dev/null
-// 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.Diagnostics.CodeAnalysis;
-
-namespace System.Text.Json.Serialization
-{
- // Used for value converters that need to re-enter the serializer since it will support JsonPath
- // and reference handling.
- internal abstract class JsonValueConverter<T> : JsonConverter<T>
- {
- internal sealed override ClassType ClassType => ClassType.NewValue;
-
- public sealed override bool HandleNull => false;
-
- [return: MaybeNull]
- public sealed override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- {
- // Bridge from resumable to value converters.
- if (options == null)
- {
- options = JsonSerializerOptions.s_defaultOptions;
- }
-
- ReadStack state = default;
- state.Initialize(typeToConvert, options, supportContinuation: false);
- TryRead(ref reader, typeToConvert, options, ref state, out T value);
- return value;
- }
-
- public sealed override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
- {
- // Bridge from resumable to value converters.
- if (options == null)
- {
- options = JsonSerializerOptions.s_defaultOptions;
- }
-
- WriteStack state = default;
- state.Initialize(typeof(T), options, supportContinuation: false);
- TryWrite(writer, value, options, ref state);
- }
- }
-}
byte[]? utf8PropertyName = frame.JsonPropertyName;
if (utf8PropertyName == null)
{
- // Attempt to get the JSON property name from the JsonPropertyInfo or JsonParameterInfo.
- utf8PropertyName = frame.JsonPropertyInfo?.NameAsUtf8Bytes ??
- frame.CtorArgumentState?.JsonParameterInfo?.NameAsUtf8Bytes;
-
- if (utf8PropertyName == null)
+ if (frame.JsonPropertyNameAsString != null)
{
// Attempt to get the JSON property name set manually for dictionary
- // keys and serializer re-entry cases where a property is specified.
+ // keys and KeyValuePair property names.
propertyName = frame.JsonPropertyNameAsString;
}
+ else
+ {
+ // Attempt to get the JSON property name from the JsonPropertyInfo or JsonParameterInfo.
+ utf8PropertyName = frame.JsonPropertyInfo?.NameAsUtf8Bytes ??
+ frame.CtorArgumentState?.JsonParameterInfo?.NameAsUtf8Bytes;
+ }
}
if (utf8PropertyName != null)
PropertyState = StackFramePropertyState.None;
}
- public void InitializeReEntry(Type type, JsonSerializerOptions options, string? propertyName)
- {
- JsonClassInfo jsonClassInfo = options.GetOrAddClass(type);
-
- // The initial JsonPropertyInfo will be used to obtain the converter.
- JsonPropertyInfo = jsonClassInfo.PropertyInfoForClassInfo;
-
- // Set for exception handling calculation of JsonPath.
- JsonPropertyNameAsString = propertyName;
- }
-
/// <summary>
/// Is the current object a Dictionary.
/// </summary>
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
+ // Although the policy won't produce these strings, the serializer successfully parses the properties.
+ // "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<string, int> kvp = JsonSerializer.Deserialize<KeyValuePair<string, int>>(json, options);
json = @"{""key"":""Hello, World!"",""value"":1}";
Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<KeyValuePair<string, int>>(json, options));
- // "Key" and "Value" matching is case sensitive, even when case sensitivity is on.
+ // "Key" and "Value" matching is case sensitive, even when case insensitivity 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
}
[Theory]
- [InlineData(typeof(KeyNameNullPolicy))]
- [InlineData(typeof(ValueNameNullPolicy))]
- public static void InvalidPropertyNameFail(Type policyType)
+ [InlineData(typeof(KeyNameNullPolicy), "Key")]
+ [InlineData(typeof(ValueNameNullPolicy), "Value")]
+ public static void InvalidPropertyNameFail(Type policyType, string offendingProperty)
{
var options = new JsonSerializerOptions
{
InvalidOperationException ex = Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize<KeyValuePair<string, string>>("", options));
string exAsStr = ex.ToString();
- Assert.Contains(policyType.ToString(), exAsStr);
+ Assert.Contains(offendingProperty, exAsStr);
Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(new KeyValuePair<string, string>("", ""), options));
}
[InlineData("{0")]
[InlineData(@"{""Random"":")]
[InlineData(@"{""Value"":1}")]
+ [InlineData(@"{null:1}")]
[InlineData(@"{""Value"":1,2")]
[InlineData(@"{""Value"":1,""Random"":")]
[InlineData(@"{""Key"":1,""Key"":1}")]
+ [InlineData(@"{null:1,""Key"":1}")]
[InlineData(@"{""Key"":1,""Key"":2}")]
[InlineData(@"{""Value"":1,""Value"":1}")]
+ [InlineData(@"{""Value"":1,null:1}")]
[InlineData(@"{""Value"":1,""Value"":2}")]
public static void InvalidJsonFail(string json)
{
Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<KeyValuePair<int, int>>(json));
}
+
+ [Theory]
+ [InlineData(@"{""Key"":""1"",""Value"":2}", "$.Key")]
+ [InlineData(@"{""Key"":1,""Value"":""2""}", "$.Value")]
+ [InlineData(@"{""key"":1,""Value"":2}", "$.key")]
+ [InlineData(@"{""Key"":1,""value"":2}", "$.value")]
+ [InlineData(@"{""Extra"":3,""Key"":1,""Value"":2}", "$.Extra")]
+ [InlineData(@"{""Key"":1,""Extra"":3,""Value"":2}", "$.Extra")]
+ [InlineData(@"{""Key"":1,""Value"":2,""Extra"":3}", "$.Extra")]
+ public static void JsonPathIsAccurate(string json, string expectedPath)
+ {
+ JsonException ex = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<KeyValuePair<int, int>>(json));
+ Assert.Contains(expectedPath, ex.ToString());
+
+ var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
+ ex = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<KeyValuePair<int, int>>(json));
+ Assert.Contains(expectedPath, ex.ToString());
+ }
+
+ [Theory]
+ [InlineData(@"{""kEy"":""1"",""vAlUe"":2}", "$.kEy")]
+ [InlineData(@"{""kEy"":1,""vAlUe"":""2""}", "$.vAlUe")]
+ public static void JsonPathIsAccurate_CaseInsensitive(string json, string expectedPath)
+ {
+ var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
+ JsonException ex = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<KeyValuePair<int, int>>(json, options));
+ Assert.Contains(expectedPath, ex.ToString());
+ }
+
+ [Theory]
+ [InlineData(@"{""_Key"":""1"",""_Value"":2}", "$._Key")]
+ [InlineData(@"{""_Key"":1,""_Value"":""2""}", "$._Value")]
+ public static void JsonPathIsAccurate_PropertyNamingPolicy(string json, string expectedPath)
+ {
+ var options = new JsonSerializerOptions { PropertyNamingPolicy = new LeadingUnderscorePolicy() };
+ JsonException ex = Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<KeyValuePair<int, int>>(json, options));
+ Assert.Contains(expectedPath, ex.ToString());
+ }
}
}
return value;
}
- KeyValuePair<int, string> kvpair = _intToStringConverter.Read(ref reader, typeToConvert, options);
+ KeyValuePair<int, string> kvpair = _intToStringConverter.Read(ref reader, typeof(KeyValuePair<int, string>), options);
value.Add(kvpair.Key, kvpair.Value);
}
return value;
}
- KeyValuePair<TKey, TValue> kv = _converter.Read(ref reader, typeToConvert, options);
+ KeyValuePair<TKey, TValue> kv = _converter.Read(ref reader, typeof(KeyValuePair<TKey, TValue>), options);
value.Add(kv.Key, kv.Value);
}
GenericConverterTestHelper<DateTime>("DateTimeConverter", new DateTime(2018, 12, 3), "\"2018-12-03T00:00:00\"", options);
GenericConverterTestHelper<DateTimeOffset>("DateTimeOffsetConverter", new DateTimeOffset(new DateTime(2018, 12, 3, 00, 00, 00, DateTimeKind.Utc)), "\"2018-12-03T00:00:00+00:00\"", options);
Guid testGuid = new Guid();
- GenericConverterTestHelper<Guid>("GuidConverter", testGuid, $"\"{testGuid.ToString()}\"", options);
- GenericConverterTestHelper<KeyValuePair<string, string>>("KeyValuePairConverter`2", new KeyValuePair<string, string>("key", "value"), @"{""Key"":""key"",""Value"":""value""}", options);
+ GenericConverterTestHelper<Guid>("GuidConverter", testGuid, $"\"{testGuid}\"", options);
GenericConverterTestHelper<Uri>("UriConverter", new Uri("http://test.com"), "\"http://test.com\"", options);
}
[Fact]
+ public static void Options_GetConverter_GivesCorrectKeyValuePairConverter()
+ {
+ GenericConverterTestHelper<KeyValuePair<string, string>>(
+ converterName: "KeyValuePairConverter`2",
+ objectValue: new KeyValuePair<string, string>("key", "value"),
+ stringValue: @"{""Key"":""key"",""Value"":""value""}",
+ options: new JsonSerializerOptions(),
+ nullOptionOkay: false);
+ }
+
+ [Fact]
public static void Options_GetConverter_GivesCorrectCustomConverterAndReadWriteSuccess()
{
var options = new JsonSerializerOptions();
GenericConverterTestHelper<long[]>("LongArrayConverter", new long[] { 1, 2, 3, 4 }, "\"1,2,3,4\"", options);
}
- private static void GenericConverterTestHelper<T>(string converterName, object objectValue, string stringValue, JsonSerializerOptions options)
+ private static void GenericConverterTestHelper<T>(string converterName, object objectValue, string stringValue, JsonSerializerOptions options, bool nullOptionOkay = true)
{
JsonConverter<T> converter = (JsonConverter<T>)options.GetConverter(typeof(T));
Utf8JsonReader reader = new Utf8JsonReader(data);
reader.Read();
- T valueRead = converter.Read(ref reader, typeof(T), null); // Test with null option.
+ T valueRead = converter.Read(ref reader, typeof(T), nullOptionOkay ? null: options);
Assert.Equal(objectValue, valueRead);
if (reader.TokenType != JsonTokenType.EndObject)
Assert.Equal(stringValue, Encoding.UTF8.GetString(stream.ToArray()));
writer.Reset(stream);
- converter.Write(writer, (T)objectValue, null); // Test with null option.
+ converter.Write(writer, (T)objectValue, nullOptionOkay ? null : options);
writer.Flush();
Assert.Equal(stringValue + stringValue, Encoding.UTF8.GetString(stream.ToArray()));
}
Assert.Throws<ArgumentOutOfRangeException>(() => new JsonSerializerOptions(outOfRangeSerializerDefaults));
}
- private static JsonSerializerOptions CreateOptionsInstance()
- {
- var options = new JsonSerializerOptions
- {
- AllowTrailingCommas = true,
- DefaultBufferSize = 20,
- DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
- Encoder = JavaScriptEncoder.Default,
- IgnoreNullValues = true,
- IgnoreReadOnlyProperties = true,
- MaxDepth = 32,
- PropertyNameCaseInsensitive = true,
- PropertyNamingPolicy = new SimpleSnakeCasePolicy(),
- ReadCommentHandling = JsonCommentHandling.Disallow,
- ReferenceHandling = ReferenceHandling.Default,
- WriteIndented = true,
- };
-
- options.Converters.Add(new JsonStringEnumConverter());
- options.Converters.Add(new ConverterForInt32());
-
- return options;
- }
-
private static JsonSerializerOptions GetFullyPopulatedOptionsInstance()
{
var options = new JsonSerializerOptions();
{
Assert.Throws<ArgumentException>(() => new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.Always });
}
+
+ [Fact]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/36605")]
+ public static void ConverterRead_VerifyInvalidTypeToConvertFails()
+ {
+ var options = new JsonSerializerOptions();
+ Type typeToConvert = typeof(KeyValuePair<int, int>);
+ byte[] bytes = Encoding.UTF8.GetBytes(@"{""Key"":1,""Value"":2}");
+
+ JsonConverter<KeyValuePair<int, int>> converter =
+ (JsonConverter<KeyValuePair<int, int>>)options.GetConverter(typeToConvert);
+
+ // Baseline
+ var reader = new Utf8JsonReader(bytes);
+ reader.Read();
+ KeyValuePair<int, int> kvp = converter.Read(ref reader, typeToConvert, options);
+ Assert.Equal(1, kvp.Key);
+ Assert.Equal(2, kvp.Value);
+
+ // Test
+ reader = new Utf8JsonReader(bytes);
+ reader.Read();
+ try
+ {
+ converter.Read(ref reader, typeof(Dictionary<string, int>), options);
+ }
+ catch (Exception ex)
+ {
+ if (!(ex is InvalidOperationException))
+ {
+ throw ex;
+ }
+ }
+ }
}
}
public static async Task HandleCollectionsAsync()
{
await RunTest<string>();
- await RunTest<ClassWithString>();
- await RunTest<ImmutableStructWithString>();
+ await RunTest<ClassWithKVP>();
+ await RunTest<ImmutableStructWithStrings>();
}
private static async Task RunTest<TElement>()
{
return ImmutableDictionary.CreateRange(GetDict_TypedElements<TElement>(stringLength));
}
+ else if (type == typeof(KeyValuePair<TElement, TElement>))
+ {
+ TElement item = GetCollectionElement<TElement>(stringLength);
+ return new KeyValuePair<TElement, TElement>(item, item);
+ }
else if (
typeof(IDictionary<string, TElement>).IsAssignableFrom(type) ||
typeof(IReadOnlyDictionary<string, TElement>).IsAssignableFrom(type) ||
}
}
- private static string GetPayloadWithWhiteSpace(string json) => json.Replace(" ", new string(' ', 4));
+ private static string GetPayloadWithWhiteSpace(string json) => json.Replace(" ", new string(' ', 8));
private const int NumElements = 15;
char randomChar = (char)rand.Next('a', 'z');
string value = new string(randomChar, stringLength);
+ var kvp = new KeyValuePair<string, SimpleStruct>(value, new SimpleStruct {
+ One = 1,
+ Two = 2
+ });
if (type == typeof(string))
{
return (TElement)(object)value;
}
- else if (type == typeof(ClassWithString))
+ else if (type == typeof(ClassWithKVP))
{
- return (TElement)(object)new ClassWithString
- {
- MyFirstString = value
- };
+ return (TElement)(object)new ClassWithKVP { MyKvp = kvp };
}
else
{
- return (TElement)(object)new ImmutableStructWithString(value, value);
+ return (TElement)(object)new ImmutableStructWithStrings(value, value);
}
throw new NotImplementedException();
{
yield return type;
}
+ foreach (Type type in ObjectNotationTypes<TElement>())
+ {
+ yield return type;
+ }
// Stack types
foreach (Type type in StackTypes<TElement>())
{
yield return typeof(Queue<TElement>); // QueueOfTConverter
}
+ private static IEnumerable<Type> ObjectNotationTypes<TElement>()
+ {
+ yield return typeof(KeyValuePair<TElement, TElement>); // KeyValuePairConverter
+ }
+
private static IEnumerable<Type> DictionaryTypes<TElement>()
{
yield return typeof(Dictionary<string, TElement>); // DictionaryOfStringTValueConverter
typeof(GenericIReadOnlyDictionaryWrapper<string, TElement>)
};
- private class ClassWithString
+ private class ClassWithKVP
{
- public string MyFirstString { get; set; }
+ public KeyValuePair<string, SimpleStruct> MyKvp { get; set; }
}
- private struct ImmutableStructWithString
+ private struct ImmutableStructWithStrings
{
public string MyFirstString { get; }
public string MySecondString { get; }
[JsonConstructor]
- public ImmutableStructWithString(string myFirstString, string mySecondString)
+ public ImmutableStructWithStrings(
+ string myFirstString, string mySecondString)
{
MyFirstString = myFirstString;
MySecondString = mySecondString;
{
Assert.Equal("{}", JsonSerializer.Serialize(GetEmptyCollection<int>(type)));
}
+
+ foreach (Type type in ObjectNotationTypes<int>())
+ {
+ Assert.Equal(@"{""Key"":0,""Value"":0}", JsonSerializer.Serialize(GetEmptyCollection<int>(type)));
+ }
}
}
}