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

Refactor root-level serialization logic and polymorphic value handling. #72789

Merged
merged 8 commits into from
Jul 29, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ internal CastingConverter(JsonConverter<TSource> sourceConverter) : base(initial
IsInternalConverterForNumberType = sourceConverter.IsInternalConverterForNumberType;
RequiresReadAhead = sourceConverter.RequiresReadAhead;
CanUseDirectReadOrWrite = sourceConverter.CanUseDirectReadOrWrite;
CanBePolymorphic = sourceConverter.CanBePolymorphic;
}

public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
Expand Down Expand Up @@ -91,6 +92,11 @@ static void HandleFailure(TSource? source)

private static TSource CastOnWrite(T source)
{
if (default(TSource) is not null && default(T) is null && source is null)
eiriktsarpalis marked this conversation as resolved.
Show resolved Hide resolved
{
ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(typeof(TSource));
}

return (TSource)(object?)source!;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,12 @@ internal override bool OnTryWrite(Utf8JsonWriter writer, T value, JsonSerializer
Debug.Assert(options == jsonTypeInfo.Options);

if (!state.SupportContinuation &&
jsonTypeInfo.HasSerializeHandler &&
jsonTypeInfo is JsonTypeInfo<T> info &&
!state.CurrentContainsMetadata && // Do not use the fast path if state needs to write metadata.
info.Options.SerializerContext?.CanUseSerializationLogic == true)
jsonTypeInfo.CanUseSerializeHandler &&
!state.CurrentContainsMetadata) // Do not use the fast path if state needs to write metadata.
{
Debug.Assert(info.SerializeHandler != null);
info.SerializeHandler(writer, value);
Debug.Assert(jsonTypeInfo is JsonTypeInfo<T> typeInfo && typeInfo.SerializeHandler != null);
Debug.Assert(options.SerializerContext?.CanUseSerializationLogic == true);
eiriktsarpalis marked this conversation as resolved.
Show resolved Hide resolved
((JsonTypeInfo<T>)jsonTypeInfo).SerializeHandler!(writer, value);
eiriktsarpalis marked this conversation as resolved.
Show resolved Hide resolved
return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,16 @@ public partial class JsonConverter
case PolymorphicSerializationState.None:
Debug.Assert(!state.IsContinuation);

if (state.IsPolymorphicRootValue && state.CurrentDepth == 0)
{
Debug.Assert(jsonTypeInfo.PolymorphicTypeResolver != null);

// We're serializing a root-level object value whose runtime type uses type hierarchies.
// For consistency with nested value handling, we want to serialize as-is without emitting metadata.
state.Current.PolymorphicSerializationState = PolymorphicSerializationState.PolymorphicReEntryNotFound;
break;
}

Type runtimeType = value.GetType();

if (jsonTypeInfo.PolymorphicTypeResolver is PolymorphicTypeResolver resolver)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public partial class JsonConverter<T>
{
if (state.SupportContinuation)
{
// If a Stream-based scenaio, return the actual value previously found;
// If a Stream-based scenario, return the actual value previously found;
// this may or may not be the final pass through here.
state.BytesConsumed += reader.BytesConsumed;
if (state.Current.ReturnValue == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ internal sealed override bool WriteCoreAsObject(
{
if (
#if NETCOREAPP
// Short-circuit the check against "is not null"; treated as a constant by recent versions of the JIT.
// Treated as a constant by recent versions of the JIT.
typeof(T).IsValueType)
#else
IsValueType)
#endif
{
// Value types can never have a null except for Nullable<T>.
if (value == null && Nullable.GetUnderlyingType(TypeToConvert) == null)
if (default(T) is not null && value is null)
{
ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(TypeToConvert);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text.Json.Serialization.Metadata;

namespace System.Text.Json.Serialization
{
/// <summary>
Expand All @@ -20,7 +22,9 @@ internal abstract class JsonResumableConverter<T> : JsonConverter<T>
// Bridge from resumable to value converters.

ReadStack state = default;
state.Initialize(typeToConvert, options, supportContinuation: false);
JsonTypeInfo jsonTypeInfo = options.GetTypeInfoInternal(typeToConvert);
state.Initialize(jsonTypeInfo);

TryRead(ref reader, typeToConvert, options, ref state, out T? value);
return value;
}
Expand All @@ -33,9 +37,10 @@ public sealed override void Write(Utf8JsonWriter writer, T value, JsonSerializer
}

// Bridge from resumable to value converters.

WriteStack state = default;
state.Initialize(typeof(T), options, supportContinuation: false, supportAsync: false);
JsonTypeInfo typeInfo = options.GetTypeInfoInternal(typeof(T));
state.Initialize(typeInfo);

try
{
TryWrite(writer, value, options, ref state);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,42 @@ public static partial class JsonSerializer

[RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)]
private static JsonTypeInfo GetTypeInfo(JsonSerializerOptions? options, Type runtimeType)
private static JsonTypeInfo GetTypeInfo(JsonSerializerOptions? options, Type inputType)
{
Debug.Assert(runtimeType != null);
Debug.Assert(inputType != null);

options ??= JsonSerializerOptions.Default;

if (!options.IsImmutable || !DefaultJsonTypeInfoResolver.IsDefaultInstanceRooted)
if (!options.IsInitializedForReflectionSerializer)
{
options.InitializeForReflectionSerializer();
}

return options.GetTypeInfoForRootType(runtimeType);
// In order to improve performance of polymorphic root-level object serialization,
// we bypass GetTypeInfoForRootType and cache JsonTypeInfo<object> in a dedicated property.
// This lets any derived types take advantage of the cache in GetTypeInfoForRootType themselves.
return inputType == JsonTypeInfo.ObjectType
? options.ObjectTypeInfo
: options.GetTypeInfoForRootType(inputType);
}

private static JsonTypeInfo GetTypeInfo(JsonSerializerContext context, Type type)
[RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)]
private static JsonTypeInfo<T> GetTypeInfo<T>(JsonSerializerOptions? options)
=> (JsonTypeInfo<T>)GetTypeInfo(options, typeof(T));

private static JsonTypeInfo GetTypeInfo(JsonSerializerContext context, Type inputType)
{
Debug.Assert(context != null);
Debug.Assert(type != null);
Debug.Assert(inputType != null);

JsonTypeInfo? info = context.GetTypeInfo(type);
JsonTypeInfo? info = context.GetTypeInfo(inputType);
if (info is null)
{
ThrowHelper.ThrowInvalidOperationException_NoMetadataForType(type, context);
ThrowHelper.ThrowInvalidOperationException_NoMetadataForType(inputType, context);
}

info.EnsureConfigured();
return info;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ public static partial class JsonSerializer
ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
}

jsonTypeInfo.EnsureConfigured();
eiriktsarpalis marked this conversation as resolved.
Show resolved Hide resolved
return ReadDocument<TValue>(document, jsonTypeInfo);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ public static partial class JsonSerializer
ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
}

jsonTypeInfo.EnsureConfigured();
return ReadUsingMetadata<TValue>(element, jsonTypeInfo);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,45 +9,45 @@ namespace System.Text.Json
{
public static partial class JsonSerializer
{
private static TValue? ReadCore<TValue>(JsonConverter jsonConverter, ref Utf8JsonReader reader, JsonSerializerOptions options, ref ReadStack state)
private static TValue? ReadCore<TValue>(ref Utf8JsonReader reader, JsonTypeInfo jsonTypeInfo, ref ReadStack state)
eiriktsarpalis marked this conversation as resolved.
Show resolved Hide resolved
{
if (jsonConverter is JsonConverter<TValue> converter)
if (jsonTypeInfo is JsonTypeInfo<TValue> typedInfo)
{
// Call the strongly-typed ReadCore that will not box structs.
return converter.ReadCore(ref reader, options, ref state);
return typedInfo.EffectiveConverter.ReadCore(ref reader, typedInfo.Options, ref state);
}

// The non-generic API was called or we have a polymorphic case where TValue is not equal to the T in JsonConverter<T>.
object? value = jsonConverter.ReadCoreAsObject(ref reader, options, ref state);
Debug.Assert(value == null || value is TValue);
// The non-generic API was called.
object? value = jsonTypeInfo.Converter.ReadCoreAsObject(ref reader, jsonTypeInfo.Options, ref state);
Debug.Assert(value is null or TValue);
return (TValue?)value;
}

private static TValue? ReadFromSpan<TValue>(ReadOnlySpan<byte> utf8Json, JsonTypeInfo jsonTypeInfo, int? actualByteCount = null)
{
Debug.Assert(jsonTypeInfo.IsConfigured);

JsonSerializerOptions options = jsonTypeInfo.Options;

var readerState = new JsonReaderState(options.GetReaderOptions());
var reader = new Utf8JsonReader(utf8Json, isFinalBlock: true, readerState);

ReadStack state = default;
jsonTypeInfo.EnsureConfigured();
state.Initialize(jsonTypeInfo);

TValue? value;
JsonConverter jsonConverter = jsonTypeInfo.Converter;

// For performance, the code below is a lifted ReadCore() above.
if (jsonConverter is JsonConverter<TValue> converter)
if (jsonTypeInfo is JsonTypeInfo<TValue> typedInfo)
{
// Call the strongly-typed ReadCore that will not box structs.
value = converter.ReadCore(ref reader, options, ref state);
value = typedInfo.EffectiveConverter.ReadCore(ref reader, options, ref state);
}
else
{
// The non-generic API was called or we have a polymorphic case where TValue is not equal to the T in JsonConverter<T>.
object? objValue = jsonConverter.ReadCoreAsObject(ref reader, options, ref state);
Debug.Assert(objValue == null || objValue is TValue);
// The non-generic API was called.
object? objValue = jsonTypeInfo.Converter.ReadCoreAsObject(ref reader, options, ref state);
Debug.Assert(objValue is null or TValue);
value = (TValue?)objValue;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ public static partial class JsonSerializer
ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
}

jsonTypeInfo.EnsureConfigured();
return ReadNode<TValue>(node, jsonTypeInfo);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ public static partial class JsonSerializer
ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
}

jsonTypeInfo.EnsureConfigured();
eiriktsarpalis marked this conversation as resolved.
Show resolved Hide resolved
return ReadFromSpan<TValue>(utf8Json, jsonTypeInfo);
}

Expand Down
Loading