Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support async KeyValuePair (de)serialization #36607

Merged
merged 3 commits into from
May 28, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 4 additions & 9 deletions src/libraries/System.Text.Json/src/System.Text.Json.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,8 @@
<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" />
layomia marked this conversation as resolved.
Show resolved Hide resolved
<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" />
Expand Down Expand Up @@ -160,7 +158,6 @@
<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" />
Expand Down Expand Up @@ -215,8 +212,7 @@
<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" />
Expand All @@ -225,8 +221,7 @@
<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" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,9 @@ internal class ArgumentState
// 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace System.Text.Json.Serialization.Converters
/// 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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ internal sealed override bool OnTryRead(ref Utf8JsonReader reader, Type typeToCo
state.Current.JsonClassInfo.UpdateSortedParameterCache(ref state.Current);
}

EndRead(ref state);

value = (T)obj;

return true;
Expand Down Expand Up @@ -440,11 +442,12 @@ private void BeginRead(ref ReadStack state, ref Utf8JsonReader reader, JsonSeria
InitializeConstructorArgumentCaches(ref state, options);
}

protected virtual void EndRead(ref ReadStack state) { }
layomia marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Lookup the constructor parameter given its name in the reader.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
layomia marked this conversation as resolved.
Show resolved Hide resolved
private bool TryLookupConstructorParameter(
protected virtual bool TryLookupConstructorParameter(
ref ReadStack state,
ref Utf8JsonReader reader,
JsonSerializerOptions options,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,29 @@
// 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>
layomia marked this conversation as resolved.
Show resolved Hide resolved
{
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;
Expand All @@ -38,107 +36,68 @@ internal override void Initialize(JsonSerializerOptions options)
_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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// 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;

Expand All @@ -22,6 +23,8 @@ public override bool CanConvert(Type typeToConvert)
[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];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,6 @@ namespace System.Text.Json
{
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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,6 @@ namespace System.Text.Json
{
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>
Expand Down

This file was deleted.

Loading