Skip to content
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)
{
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);
((JsonTypeInfo<T>)jsonTypeInfo).SerializeHandler!(writer, value);
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();
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)
{
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();
return ReadFromSpan<TValue>(utf8Json, jsonTypeInfo);
}

Expand Down
Loading