* Refactor root-level serialization logic and polymorphic value handling.
* Address feedback
* Do not consult metadata LRU cache in JsonResumableConverter bridging logic.
* Use secondary LRU cache when resolving root-level polymorphic types.
* Add test coverage for root-level polymorphic values in JsonTypeInfo<T> JsonSerializer methods.
* Change caching strategy for root-level polymorphic values.
* Update src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Helpers.cs
* Remove commented out code
IsInternalConverterForNumberType = sourceConverter.IsInternalConverterForNumberType;
RequiresReadAhead = sourceConverter.RequiresReadAhead;
CanUseDirectReadOrWrite = sourceConverter.CanUseDirectReadOrWrite;
+ CanBePolymorphic = sourceConverter.CanBePolymorphic;
}
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
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!;
}
}
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;
}
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)
{
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)
{
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);
}
// 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>
// 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;
}
}
// 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);
[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;
}
ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
}
+ jsonTypeInfo.EnsureConfigured();
return ReadDocument<TValue>(document, jsonTypeInfo);
}
ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
}
+ jsonTypeInfo.EnsureConfigured();
return ReadUsingMetadata<TValue>(element, jsonTypeInfo);
}
{
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;
}
ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
}
+ jsonTypeInfo.EnsureConfigured();
return ReadNode<TValue>(node, jsonTypeInfo);
}
ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
}
+ jsonTypeInfo.EnsureConfigured();
return ReadFromSpan<TValue>(utf8Json, jsonTypeInfo);
}
using System.IO;
using System.Runtime.CompilerServices;
using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Converters;
using System.Text.Json.Serialization.Metadata;
using System.Threading;
using System.Threading.Tasks;
}
JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, typeof(TValue));
- return ReadAllAsync<TValue>(utf8Json, jsonTypeInfo, cancellationToken);
+ return ReadFromStreamAsync<TValue>(utf8Json, jsonTypeInfo, cancellationToken);
}
/// <summary>
ThrowHelper.ThrowArgumentNullException(nameof(utf8Json));
}
- return ReadAllUsingOptions<TValue>(utf8Json, typeof(TValue), options);
+ JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, typeof(TValue));
+ return ReadFromStream<TValue>(utf8Json, jsonTypeInfo);
}
/// <summary>
}
JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, returnType);
- return ReadAllAsync<object?>(utf8Json, jsonTypeInfo, cancellationToken);
+ return ReadFromStreamAsync<object?>(utf8Json, jsonTypeInfo, cancellationToken);
}
/// <summary>
ThrowHelper.ThrowArgumentNullException(nameof(returnType));
}
- return ReadAllUsingOptions<object>(utf8Json, returnType, options);
+ JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, returnType);
+ return ReadFromStream<object>(utf8Json, jsonTypeInfo);
}
/// <summary>
ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
}
- return ReadAllAsync<TValue>(utf8Json, jsonTypeInfo, cancellationToken);
+ jsonTypeInfo.EnsureConfigured();
+ return ReadFromStreamAsync<TValue>(utf8Json, jsonTypeInfo, cancellationToken);
}
/// <summary>
ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
}
- return ReadAll<TValue>(utf8Json, jsonTypeInfo);
+ jsonTypeInfo.EnsureConfigured();
+ return ReadFromStream<TValue>(utf8Json, jsonTypeInfo);
}
/// <summary>
ThrowHelper.ThrowArgumentNullException(nameof(context));
}
- return ReadAllAsync<object>(utf8Json, GetTypeInfo(context, returnType), cancellationToken);
+ JsonTypeInfo jsonTypeInfo = GetTypeInfo(context, returnType);
+ return ReadFromStreamAsync<object>(utf8Json, jsonTypeInfo, cancellationToken);
}
/// <summary>
ThrowHelper.ThrowArgumentNullException(nameof(context));
}
- return ReadAll<object>(utf8Json, GetTypeInfo(context, returnType));
+ JsonTypeInfo jsonTypeInfo = GetTypeInfo(context, returnType);
+ return ReadFromStream<object>(utf8Json, jsonTypeInfo);
}
/// <summary>
private static JsonTypeInfo<Queue<TValue>> CreateQueueTypeInfo<TValue>(JsonTypeInfo jsonTypeInfo)
{
- return JsonMetadataServices.CreateQueueInfo<Queue<TValue>, TValue>(
+ JsonTypeInfo<Queue<TValue>> queueTypeInfo = JsonMetadataServices.CreateQueueInfo<Queue<TValue>, TValue>(
options: jsonTypeInfo.Options,
collectionInfo: new()
{
ElementInfo = jsonTypeInfo,
NumberHandling = jsonTypeInfo.Options.NumberHandling
});
+
+ queueTypeInfo.EnsureConfigured();
+ return queueTypeInfo;
}
private static async IAsyncEnumerable<TValue> CreateAsyncEnumerableDeserializer<TValue>(
JsonTypeInfo<Queue<TValue>> queueTypeInfo,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
- queueTypeInfo.EnsureConfigured();
+ Debug.Assert(queueTypeInfo.IsConfigured);
JsonSerializerOptions options = queueTypeInfo.Options;
var bufferState = new ReadBufferState(options.DefaultBufferSize);
- ReadStack readStack = default;
- readStack.Initialize(queueTypeInfo, supportContinuation: true);
+ ReadStack readStack = new ReadStack
+ {
+ SupportContinuation = true
+ };
+
+ readStack.Initialize(queueTypeInfo);
+
var jsonReaderState = new JsonReaderState(options.GetReaderOptions());
try
ref bufferState,
ref jsonReaderState,
ref readStack,
- queueTypeInfo.Converter,
- options);
+ queueTypeInfo);
if (readStack.Current.ReturnValue is Queue<TValue> queue)
{
}
}
- internal static async ValueTask<TValue?> ReadAllAsync<TValue>(
+ internal static async ValueTask<TValue?> ReadFromStreamAsync<TValue>(
Stream utf8Json,
JsonTypeInfo jsonTypeInfo,
CancellationToken cancellationToken)
{
+ Debug.Assert(jsonTypeInfo.IsConfigured);
JsonSerializerOptions options = jsonTypeInfo.Options;
var bufferState = new ReadBufferState(options.DefaultBufferSize);
- ReadStack readStack = default;
- jsonTypeInfo.EnsureConfigured();
- readStack.Initialize(jsonTypeInfo, supportContinuation: true);
- JsonConverter converter = readStack.Current.JsonPropertyInfo!.EffectiveConverter;
+ ReadStack readStack = new ReadStack
+ {
+ SupportContinuation = true
+ };
+ readStack.Initialize(jsonTypeInfo);
var jsonReaderState = new JsonReaderState(options.GetReaderOptions());
try
while (true)
{
bufferState = await bufferState.ReadFromStreamAsync(utf8Json, cancellationToken).ConfigureAwait(false);
- TValue value = ContinueDeserialize<TValue>(ref bufferState, ref jsonReaderState, ref readStack, converter, options);
+ TValue? value = ContinueDeserialize<TValue>(ref bufferState, ref jsonReaderState, ref readStack, jsonTypeInfo);
if (bufferState.IsFinalBlock)
{
- return value!;
+ return value;
}
}
}
}
}
- internal static TValue? ReadAll<TValue>(
+ internal static TValue? ReadFromStream<TValue>(
Stream utf8Json,
JsonTypeInfo jsonTypeInfo)
{
+ Debug.Assert(jsonTypeInfo.IsConfigured);
JsonSerializerOptions options = jsonTypeInfo.Options;
var bufferState = new ReadBufferState(options.DefaultBufferSize);
- ReadStack readStack = default;
- jsonTypeInfo.EnsureConfigured();
- readStack.Initialize(jsonTypeInfo, supportContinuation: true);
- JsonConverter converter = readStack.Current.JsonPropertyInfo!.EffectiveConverter;
+ ReadStack readStack = new ReadStack
+ {
+ SupportContinuation = true
+ };
+ readStack.Initialize(jsonTypeInfo);
var jsonReaderState = new JsonReaderState(options.GetReaderOptions());
try
while (true)
{
bufferState.ReadFromStream(utf8Json);
- TValue value = ContinueDeserialize<TValue>(ref bufferState, ref jsonReaderState, ref readStack, converter, options);
+ TValue? value = ContinueDeserialize<TValue>(ref bufferState, ref jsonReaderState, ref readStack, jsonTypeInfo);
if (bufferState.IsFinalBlock)
{
- return value!;
+ return value;
}
}
}
}
}
- internal static TValue ContinueDeserialize<TValue>(
+ internal static TValue? ContinueDeserialize<TValue>(
ref ReadBufferState bufferState,
ref JsonReaderState jsonReaderState,
ref ReadStack readStack,
- JsonConverter converter,
- JsonSerializerOptions options)
- {
- // Process the data available
- TValue value = ReadCore<TValue>(
- ref jsonReaderState,
- bufferState.IsFinalBlock,
- bufferState.Bytes,
- options,
- ref readStack,
- converter);
-
- Debug.Assert(readStack.BytesConsumed <= bufferState.Bytes.Length);
- bufferState.AdvanceBuffer((int)readStack.BytesConsumed);
-
- return value;
- }
-
- [RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)]
- [RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)]
- private static TValue? ReadAllUsingOptions<TValue>(
- Stream utf8Json,
- Type returnType,
- JsonSerializerOptions? options)
- {
- JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, returnType);
- return ReadAll<TValue>(utf8Json, jsonTypeInfo);
- }
-
- private static TValue ReadCore<TValue>(
- ref JsonReaderState readerState,
- bool isFinalBlock,
- ReadOnlySpan<byte> buffer,
- JsonSerializerOptions options,
- ref ReadStack state,
- JsonConverter converterBase)
+ JsonTypeInfo jsonTypeInfo)
{
- var reader = new Utf8JsonReader(buffer, isFinalBlock, readerState);
+ var reader = new Utf8JsonReader(bufferState.Bytes, bufferState.IsFinalBlock, jsonReaderState);
// If we haven't read in the entire stream's payload we'll need to signify that we want
// to enable read ahead behaviors to ensure we have complete json objects and arrays
// ({}, []) when needed. (Notably to successfully parse JsonElement via JsonDocument
// to assign to object and JsonElement properties in the constructed .NET object.)
- state.ReadAhead = !isFinalBlock;
- state.BytesConsumed = 0;
+ readStack.ReadAhead = !bufferState.IsFinalBlock;
+ readStack.BytesConsumed = 0;
- TValue? value = ReadCore<TValue>(converterBase, ref reader, options, ref state);
- readerState = reader.CurrentState;
- return value!;
+ TValue? value = ReadCore<TValue>(ref reader, jsonTypeInfo, ref readStack);
+ Debug.Assert(readStack.BytesConsumed <= bufferState.Bytes.Length);
+ bufferState.AdvanceBuffer((int)readStack.BytesConsumed);
+ jsonReaderState = reader.CurrentState;
+ return value;
}
}
}
// The .NET Foundation licenses this file to you under the MIT license.
using System.Buffers;
+using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
}
+ jsonTypeInfo.EnsureConfigured();
return ReadFromSpan<TValue?>(json.AsSpan(), jsonTypeInfo);
}
ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
}
+ jsonTypeInfo.EnsureConfigured();
return ReadFromSpan<TValue?>(json, jsonTypeInfo);
}
private static TValue? ReadFromSpan<TValue>(ReadOnlySpan<char> json, JsonTypeInfo jsonTypeInfo)
{
- jsonTypeInfo.EnsureConfigured();
+ Debug.Assert(jsonTypeInfo.IsConfigured);
byte[]? tempArray = null;
// For performance, avoid obtaining actual byte count unless memory usage is higher than the threshold.
ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
}
+ jsonTypeInfo.EnsureConfigured();
return Read<TValue>(ref reader, jsonTypeInfo);
}
private static TValue? Read<TValue>(ref Utf8JsonReader reader, JsonTypeInfo jsonTypeInfo)
{
+ Debug.Assert(jsonTypeInfo.IsConfigured);
ReadStack state = default;
- jsonTypeInfo.EnsureConfigured();
state.Initialize(jsonTypeInfo);
JsonReaderState readerState = reader.CurrentState;
var newReader = new Utf8JsonReader(rentedSpan, originalReaderOptions);
- JsonConverter jsonConverter = state.Current.JsonPropertyInfo!.EffectiveConverter;
- TValue? value = ReadCore<TValue>(jsonConverter, ref newReader, jsonTypeInfo.Options, ref state);
+ TValue? value = ReadCore<TValue>(ref newReader, jsonTypeInfo, ref state);
// The reader should have thrown if we have remaining bytes.
Debug.Assert(newReader.BytesConsumed == length);
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
TValue value,
JsonSerializerOptions? options = null)
{
- Type runtimeType = GetRuntimeType(value);
- JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType);
- return WriteBytesUsingSerializer(value, jsonTypeInfo);
+ JsonTypeInfo<TValue> jsonTypeInfo = GetTypeInfo<TValue>(options);
+ return WriteBytes(value, jsonTypeInfo);
}
/// <summary>
Type inputType,
JsonSerializerOptions? options = null)
{
- Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType);
- JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType);
- return WriteBytesUsingSerializer(value, jsonTypeInfo);
+ ValidateInputType(value, inputType);
+ JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, inputType);
+ return WriteBytesAsObject(value, jsonTypeInfo);
}
/// <summary>
ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
}
- return WriteBytesUsingGeneratedSerializer(value, jsonTypeInfo);
+ jsonTypeInfo.EnsureConfigured();
+ return WriteBytes(value, jsonTypeInfo);
}
/// <summary>
ThrowHelper.ThrowArgumentNullException(nameof(context));
}
- Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType);
- JsonTypeInfo jsonTypeInfo = GetTypeInfo(context, runtimeType);
- return WriteBytesUsingGeneratedSerializer(value!, jsonTypeInfo);
+ ValidateInputType(value, inputType);
+ JsonTypeInfo jsonTypeInfo = GetTypeInfo(context, inputType);
+ return WriteBytesAsObject(value, jsonTypeInfo);
}
- private static byte[] WriteBytesUsingGeneratedSerializer<TValue>(in TValue value, JsonTypeInfo jsonTypeInfo)
+ private static byte[] WriteBytes<TValue>(in TValue value, JsonTypeInfo<TValue> jsonTypeInfo)
{
+ Debug.Assert(jsonTypeInfo.IsConfigured);
+
JsonSerializerOptions options = jsonTypeInfo.Options;
using var output = new PooledByteBufferWriter(options.DefaultBufferSize);
- using (var writer = new Utf8JsonWriter(output, options.GetWriterOptions()))
- {
- WriteUsingGeneratedSerializer(writer, value, jsonTypeInfo);
- }
+ using var writer = new Utf8JsonWriter(output, options.GetWriterOptions());
+ WriteCore(writer, value, jsonTypeInfo);
return output.WrittenMemory.ToArray();
}
- private static byte[] WriteBytesUsingSerializer<TValue>(in TValue value, JsonTypeInfo jsonTypeInfo)
+ private static byte[] WriteBytesAsObject(object? value, JsonTypeInfo jsonTypeInfo)
{
+ Debug.Assert(jsonTypeInfo.IsConfigured);
+
JsonSerializerOptions options = jsonTypeInfo.Options;
using var output = new PooledByteBufferWriter(options.DefaultBufferSize);
- using (var writer = new Utf8JsonWriter(output, options.GetWriterOptions()))
- {
- WriteUsingSerializer(writer, value, jsonTypeInfo);
- }
-
+ using var writer = new Utf8JsonWriter(output, options.GetWriterOptions());
+ WriteCoreAsObject(writer, value, jsonTypeInfo);
return output.WrittenMemory.ToArray();
}
}
[RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)]
public static JsonDocument SerializeToDocument<TValue>(TValue value, JsonSerializerOptions? options = null)
{
- Type runtimeType = GetRuntimeType(value);
- JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType);
- return WriteDocumentUsingSerializer(value, jsonTypeInfo);
+ JsonTypeInfo<TValue> jsonTypeInfo = GetTypeInfo<TValue>(options);
+ return WriteDocument(value, jsonTypeInfo);
}
/// <summary>
[RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)]
public static JsonDocument SerializeToDocument(object? value, Type inputType, JsonSerializerOptions? options = null)
{
- Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType);
- JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType);
- return WriteDocumentUsingSerializer(value, jsonTypeInfo);
+ ValidateInputType(value, inputType);
+ JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, inputType);
+ return WriteDocumentAsObject(value, jsonTypeInfo);
}
/// <summary>
ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
}
- return WriteDocumentUsingGeneratedSerializer(value, jsonTypeInfo);
+ jsonTypeInfo.EnsureConfigured();
+ return WriteDocument(value, jsonTypeInfo);
}
/// <summary>
ThrowHelper.ThrowArgumentNullException(nameof(context));
}
- Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType);
- return WriteDocumentUsingGeneratedSerializer(value, GetTypeInfo(context, runtimeType));
+ ValidateInputType(value, inputType);
+ return WriteDocumentAsObject(value, GetTypeInfo(context, inputType));
}
- private static JsonDocument WriteDocumentUsingGeneratedSerializer<TValue>(in TValue value, JsonTypeInfo jsonTypeInfo)
+ private static JsonDocument WriteDocument<TValue>(in TValue value, JsonTypeInfo<TValue> jsonTypeInfo)
{
+ Debug.Assert(jsonTypeInfo.IsConfigured);
JsonSerializerOptions options = jsonTypeInfo.Options;
- Debug.Assert(options != null);
// For performance, share the same buffer across serialization and deserialization.
// The PooledByteBufferWriter is cleared and returned when JsonDocument.Dispose() is called.
PooledByteBufferWriter output = new(options.DefaultBufferSize);
- using (Utf8JsonWriter writer = new(output, options.GetWriterOptions()))
- {
- WriteUsingGeneratedSerializer(writer, value, jsonTypeInfo);
- }
+ using Utf8JsonWriter writer = new(output, options.GetWriterOptions());
+ WriteCore(writer, value, jsonTypeInfo);
return JsonDocument.ParseRented(output, options.GetDocumentOptions());
}
- private static JsonDocument WriteDocumentUsingSerializer<TValue>(in TValue value, JsonTypeInfo jsonTypeInfo)
+ private static JsonDocument WriteDocumentAsObject(object? value, JsonTypeInfo jsonTypeInfo)
{
+ Debug.Assert(jsonTypeInfo.IsConfigured);
JsonSerializerOptions options = jsonTypeInfo.Options;
- Debug.Assert(options != null);
// For performance, share the same buffer across serialization and deserialization.
// The PooledByteBufferWriter is cleared and returned when JsonDocument.Dispose() is called.
PooledByteBufferWriter output = new(options.DefaultBufferSize);
- using (Utf8JsonWriter writer = new(output, options.GetWriterOptions()))
- {
- WriteUsingSerializer(writer, value, jsonTypeInfo);
- }
+ using Utf8JsonWriter writer = new(output, options.GetWriterOptions());
+ WriteCoreAsObject(writer, value, jsonTypeInfo);
return JsonDocument.ParseRented(output, options.GetDocumentOptions());
}
}
[RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)]
public static JsonElement SerializeToElement<TValue>(TValue value, JsonSerializerOptions? options = null)
{
- Type runtimeType = GetRuntimeType(value);
- JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType);
- return WriteElementUsingSerializer(value, jsonTypeInfo);
+ JsonTypeInfo<TValue> jsonTypeInfo = GetTypeInfo<TValue>(options);
+ return WriteElement(value, jsonTypeInfo);
}
/// <summary>
[RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)]
public static JsonElement SerializeToElement(object? value, Type inputType, JsonSerializerOptions? options = null)
{
- Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType);
- JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType);
- return WriteElementUsingSerializer(value, jsonTypeInfo);
+ ValidateInputType(value, inputType);
+ JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, inputType);
+ return WriteElementAsObject(value, jsonTypeInfo);
}
/// <summary>
ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
}
- return WriteElementUsingGeneratedSerializer(value, jsonTypeInfo);
+ jsonTypeInfo.EnsureConfigured();
+ return WriteElement(value, jsonTypeInfo);
}
/// <summary>
ThrowHelper.ThrowArgumentNullException(nameof(context));
}
- Type type = GetRuntimeTypeAndValidateInputType(value, inputType);
- JsonTypeInfo typeInfo = GetTypeInfo(context, type);
- return WriteElementUsingGeneratedSerializer(value, typeInfo);
+ ValidateInputType(value, inputType);
+ JsonTypeInfo typeInfo = GetTypeInfo(context, inputType);
+ return WriteElementAsObject(value, typeInfo);
}
- private static JsonElement WriteElementUsingGeneratedSerializer<TValue>(in TValue value, JsonTypeInfo jsonTypeInfo)
+ private static JsonElement WriteElement<TValue>(in TValue value, JsonTypeInfo<TValue> jsonTypeInfo)
{
+ Debug.Assert(jsonTypeInfo.IsConfigured);
JsonSerializerOptions options = jsonTypeInfo.Options;
- Debug.Assert(options != null);
// For performance, share the same buffer across serialization and deserialization.
using var output = new PooledByteBufferWriter(options.DefaultBufferSize);
- using (var writer = new Utf8JsonWriter(output, options.GetWriterOptions()))
- {
- WriteUsingGeneratedSerializer(writer, value, jsonTypeInfo);
- }
+ using var writer = new Utf8JsonWriter(output, options.GetWriterOptions());
+ WriteCore(writer, value, jsonTypeInfo);
return JsonElement.ParseValue(output.WrittenMemory.Span, options.GetDocumentOptions());
}
- private static JsonElement WriteElementUsingSerializer<TValue>(in TValue value, JsonTypeInfo jsonTypeInfo)
+ private static JsonElement WriteElementAsObject(object? value, JsonTypeInfo jsonTypeInfo)
{
JsonSerializerOptions options = jsonTypeInfo.Options;
Debug.Assert(options != null);
// For performance, share the same buffer across serialization and deserialization.
using var output = new PooledByteBufferWriter(options.DefaultBufferSize);
- using (var writer = new Utf8JsonWriter(output, options.GetWriterOptions()))
- {
- WriteUsingSerializer(writer, value, jsonTypeInfo);
- }
+ using var writer = new Utf8JsonWriter(output, options.GetWriterOptions());
+ WriteCoreAsObject(writer, value, jsonTypeInfo);
return JsonElement.ParseValue(output.WrittenMemory.Span, options.GetDocumentOptions());
}
}
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
-using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Converters;
using System.Text.Json.Serialization.Metadata;
namespace System.Text.Json
{
public static partial class JsonSerializer
{
- private static bool WriteCore<TValue>(
- JsonConverter jsonConverter,
+ /// <summary>
+ /// Sync, strongly typed root value serialization helper.
+ /// </summary>
+ private static void WriteCore<TValue>(
Utf8JsonWriter writer,
in TValue value,
- JsonSerializerOptions options,
- ref WriteStack state)
+ JsonTypeInfo<TValue> jsonTypeInfo)
{
- Debug.Assert(writer != null);
+ if (jsonTypeInfo.CanUseSerializeHandler)
+ {
+ // Short-circuit calls into SerializeHandler, if supported.
+ // Even though this is already handled by JsonMetadataServicesConverter,
+ // we avoid instantiating a WriteStack and a couple of additional virtual calls.
- bool success;
+ Debug.Assert(jsonTypeInfo.SerializeHandler != null);
+ Debug.Assert(jsonTypeInfo.Options.SerializerContext?.CanUseSerializationLogic == true);
+ Debug.Assert(jsonTypeInfo.Converter is JsonMetadataServicesConverter<TValue>);
- if (jsonConverter is JsonConverter<TValue> converter)
- {
- // Call the strongly-typed WriteCore that will not box structs.
- success = converter.WriteCore(writer, value, options, ref state);
+ jsonTypeInfo.SerializeHandler(writer, value);
}
else
{
- // The non-generic API was called or we have a polymorphic case where TValue is not equal to the T in JsonConverter<T>.
- success = jsonConverter.WriteCoreAsObject(writer, value, options, ref state);
+ WriteStack state = default;
+ JsonTypeInfo polymorphicTypeInfo = ResolvePolymorphicTypeInfo(value, jsonTypeInfo, out state.IsPolymorphicRootValue);
+ state.Initialize(polymorphicTypeInfo);
+
+ bool success =
+ state.IsPolymorphicRootValue
+ ? polymorphicTypeInfo.Converter.WriteCoreAsObject(writer, value, jsonTypeInfo.Options, ref state)
+ : jsonTypeInfo.EffectiveConverter.WriteCore(writer, value, jsonTypeInfo.Options, ref state);
+
+ Debug.Assert(success);
}
writer.Flush();
- return success;
}
- private static void WriteUsingGeneratedSerializer<TValue>(Utf8JsonWriter writer, in TValue value, JsonTypeInfo jsonTypeInfo)
+ /// <summary>
+ /// Sync, untyped root value serialization helper.
+ /// </summary>
+ private static void WriteCoreAsObject(
+ Utf8JsonWriter writer,
+ object? value,
+ JsonTypeInfo jsonTypeInfo)
{
- Debug.Assert(writer != null);
+ WriteStack state = default;
+ JsonTypeInfo polymorphicTypeInfo = ResolvePolymorphicTypeInfo(value, jsonTypeInfo, out state.IsPolymorphicRootValue);
+ state.Initialize(polymorphicTypeInfo);
- if (jsonTypeInfo.HasSerializeHandler &&
- jsonTypeInfo is JsonTypeInfo<TValue> typedInfo &&
- typedInfo.Options.SerializerContext?.CanUseSerializationLogic == true)
- {
- Debug.Assert(typedInfo.SerializeHandler != null);
- typedInfo.SerializeHandler(writer, value);
- writer.Flush();
- }
- else
- {
- WriteUsingSerializer(writer, value, jsonTypeInfo);
- }
+ bool success = polymorphicTypeInfo.Converter.WriteCoreAsObject(writer, value, jsonTypeInfo.Options, ref state);
+ Debug.Assert(success);
+ writer.Flush();
}
- private static void WriteUsingSerializer<TValue>(Utf8JsonWriter writer, in TValue value, JsonTypeInfo jsonTypeInfo)
+ /// <summary>
+ /// Streaming root-level serialization helper.
+ /// </summary>
+ private static bool WriteCore<TValue>(Utf8JsonWriter writer, in TValue value, JsonTypeInfo jsonTypeInfo, ref WriteStack state)
{
- Debug.Assert(writer != null);
+ Debug.Assert(state.SupportContinuation);
- // TODO unify method with WriteUsingGeneratedSerializer
-
- WriteStack state = default;
- jsonTypeInfo.EnsureConfigured();
- state.Initialize(jsonTypeInfo, supportContinuation: false, supportAsync: false);
-
- JsonConverter converter = jsonTypeInfo.Converter;
- Debug.Assert(converter != null);
- Debug.Assert(jsonTypeInfo.Options != null);
-
- // For performance, the code below is a lifted WriteCore() above.
- if (converter is JsonConverter<TValue> typedConverter)
+ bool isFinalBlock;
+ if (jsonTypeInfo is JsonTypeInfo<TValue> typedInfo)
{
- // Call the strongly-typed WriteCore that will not box structs.
- typedConverter.WriteCore(writer, value, jsonTypeInfo.Options, ref state);
+ isFinalBlock = typedInfo.EffectiveConverter.WriteCore(writer, value, jsonTypeInfo.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>.
- converter.WriteCoreAsObject(writer, value, jsonTypeInfo.Options, ref state);
+ // The non-generic API was called.
+ isFinalBlock = jsonTypeInfo.Converter.WriteCoreAsObject(writer, value, jsonTypeInfo.Options, ref state);
}
writer.Flush();
+ return isFinalBlock;
}
- private static Type GetRuntimeType<TValue>(in TValue value)
+ private static JsonTypeInfo ResolvePolymorphicTypeInfo<TValue>(in TValue value, JsonTypeInfo jsonTypeInfo, out bool isPolymorphicType)
{
- Type type = typeof(TValue);
- if (type == JsonTypeInfo.ObjectType && value is not null)
+ if (
+#if NETCOREAPP
+ !typeof(TValue).IsValueType &&
+#endif
+ jsonTypeInfo.Converter.CanBePolymorphic && value is not null)
{
- type = value.GetType();
+ Debug.Assert(typeof(TValue) == typeof(object));
+
+ Type runtimeType = value.GetType();
+ if (runtimeType != jsonTypeInfo.Type)
+ {
+ isPolymorphicType = true;
+ return jsonTypeInfo.Options.GetTypeInfoForRootType(runtimeType);
+ }
}
- return type;
+ isPolymorphicType = false;
+ return jsonTypeInfo;
}
- private static Type GetRuntimeTypeAndValidateInputType(object? value, Type inputType)
+ private static void ValidateInputType(object? value, Type inputType)
{
if (inputType is null)
{
{
ThrowHelper.ThrowArgumentException_DeserializeWrongType(inputType, value);
}
-
- if (inputType == JsonTypeInfo.ObjectType)
- {
- return runtimeType;
- }
}
-
- return inputType;
}
}
}
[RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)]
public static JsonNode? SerializeToNode<TValue>(TValue value, JsonSerializerOptions? options = null)
{
- Type runtimeType = GetRuntimeType(value);
- JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType);
- return WriteNodeUsingSerializer(value, jsonTypeInfo);
+ JsonTypeInfo<TValue> jsonTypeInfo = GetTypeInfo<TValue>(options);
+ return WriteNode(value, jsonTypeInfo);
}
/// <summary>
[RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)]
public static JsonNode? SerializeToNode(object? value, Type inputType, JsonSerializerOptions? options = null)
{
- Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType);
- JsonTypeInfo typeInfo = GetTypeInfo(options, runtimeType);
- return WriteNodeUsingSerializer(value, typeInfo);
+ ValidateInputType(value, inputType);
+ JsonTypeInfo typeInfo = GetTypeInfo(options, inputType);
+ return WriteNodeAsObject(value, typeInfo);
}
/// <summary>
ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
}
- return WriteNodeUsingGeneratedSerializer(value, jsonTypeInfo);
+ jsonTypeInfo.EnsureConfigured();
+ return WriteNode(value, jsonTypeInfo);
}
/// <summary>
ThrowHelper.ThrowArgumentNullException(nameof(context));
}
- Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType);
- JsonTypeInfo jsonTypeInfo = GetTypeInfo(context, runtimeType);
- return WriteNodeUsingGeneratedSerializer(value, jsonTypeInfo);
+ ValidateInputType(value, inputType);
+ JsonTypeInfo jsonTypeInfo = GetTypeInfo(context, inputType);
+ return WriteNodeAsObject(value, jsonTypeInfo);
}
- private static JsonNode? WriteNodeUsingGeneratedSerializer<TValue>(in TValue value, JsonTypeInfo jsonTypeInfo)
+ private static JsonNode? WriteNode<TValue>(in TValue value, JsonTypeInfo<TValue> jsonTypeInfo)
{
+ Debug.Assert(jsonTypeInfo.IsConfigured);
JsonSerializerOptions options = jsonTypeInfo.Options;
- Debug.Assert(options != null);
// For performance, share the same buffer across serialization and deserialization.
using var output = new PooledByteBufferWriter(options.DefaultBufferSize);
- using (var writer = new Utf8JsonWriter(output, options.GetWriterOptions()))
- {
- WriteUsingGeneratedSerializer(writer, value, jsonTypeInfo);
- }
+ using var writer = new Utf8JsonWriter(output, options.GetWriterOptions());
+ WriteCore(writer, value, jsonTypeInfo);
return JsonNode.Parse(output.WrittenMemory.Span, options.GetNodeOptions(), options.GetDocumentOptions());
}
- private static JsonNode? WriteNodeUsingSerializer<TValue>(in TValue value, JsonTypeInfo jsonTypeInfo)
+ private static JsonNode? WriteNodeAsObject(object? value, JsonTypeInfo jsonTypeInfo)
{
+ Debug.Assert(jsonTypeInfo.IsConfigured);
JsonSerializerOptions options = jsonTypeInfo.Options;
- Debug.Assert(options != null);
// For performance, share the same buffer across serialization and deserialization.
using var output = new PooledByteBufferWriter(options.DefaultBufferSize);
- using (var writer = new Utf8JsonWriter(output, options.GetWriterOptions()))
- {
- WriteUsingSerializer(writer, value, jsonTypeInfo);
- }
+ using var writer = new Utf8JsonWriter(output, options.GetWriterOptions());
+ WriteCoreAsObject(writer, value, jsonTypeInfo);
return JsonNode.Parse(output.WrittenMemory.Span, options.GetNodeOptions(), options.GetDocumentOptions());
}
}
ThrowHelper.ThrowArgumentNullException(nameof(utf8Json));
}
- Type runtimeType = GetRuntimeType(value);
- JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType);
- return WriteStreamAsync(utf8Json, value!, jsonTypeInfo, cancellationToken);
+ JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, typeof(TValue));
+ return WriteStreamAsync(utf8Json, value, jsonTypeInfo, cancellationToken);
}
/// <summary>
ThrowHelper.ThrowArgumentNullException(nameof(utf8Json));
}
- Type runtimeType = GetRuntimeType(value);
- JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType);
- WriteStream(utf8Json, value!, jsonTypeInfo);
+ JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, typeof(TValue));
+ WriteStream(utf8Json, value, jsonTypeInfo);
}
/// <summary>
ThrowHelper.ThrowArgumentNullException(nameof(utf8Json));
}
- Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType);
- JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType);
- return WriteStreamAsync(utf8Json, value!, jsonTypeInfo, cancellationToken);
+ ValidateInputType(value, inputType);
+ JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, inputType);
+ return WriteStreamAsync(utf8Json, value, jsonTypeInfo, cancellationToken);
}
/// <summary>
ThrowHelper.ThrowArgumentNullException(nameof(utf8Json));
}
- Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType);
- JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType);
- WriteStream(utf8Json, value!, jsonTypeInfo);
+ ValidateInputType(value, inputType);
+ JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, inputType);
+ WriteStream(utf8Json, value, jsonTypeInfo);
}
/// <summary>
ThrowHelper.ThrowArgumentNullException(nameof(context));
}
- Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType);
+ ValidateInputType(value, inputType);
return WriteStreamAsync(
utf8Json,
- value!,
- GetTypeInfo(context, runtimeType),
+ value,
+ GetTypeInfo(context, inputType),
cancellationToken);
}
ThrowHelper.ThrowArgumentNullException(nameof(context));
}
- Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType);
- WriteStream(utf8Json, value!, GetTypeInfo(context, runtimeType));
+ ValidateInputType(value, inputType);
+ WriteStream(utf8Json, value, GetTypeInfo(context, inputType));
}
private static async Task WriteStreamAsync<TValue>(
JsonTypeInfo jsonTypeInfo,
CancellationToken cancellationToken)
{
+ jsonTypeInfo.EnsureConfigured();
JsonSerializerOptions options = jsonTypeInfo.Options;
JsonWriterOptions writerOptions = options.GetWriterOptions();
using (var bufferWriter = new PooledByteBufferWriter(options.DefaultBufferSize))
using (var writer = new Utf8JsonWriter(bufferWriter, writerOptions))
{
- WriteStack state = new WriteStack { CancellationToken = cancellationToken };
- jsonTypeInfo.EnsureConfigured();
- JsonConverter converter = state.Initialize(jsonTypeInfo, supportContinuation: true, supportAsync: true);
+ WriteStack state = new WriteStack
+ {
+ CancellationToken = cancellationToken,
+ SupportContinuation = true,
+ SupportAsync = true,
+ };
+
+ jsonTypeInfo = ResolvePolymorphicTypeInfo(value, jsonTypeInfo, out state.IsPolymorphicRootValue);
+ state.Initialize(jsonTypeInfo);
bool isFinalBlock;
try
{
- isFinalBlock = WriteCore(converter, writer, value, options, ref state);
+ isFinalBlock = WriteCore(writer, value, jsonTypeInfo, ref state);
if (state.SuppressFlush)
{
in TValue value,
JsonTypeInfo jsonTypeInfo)
{
+ jsonTypeInfo.EnsureConfigured();
JsonSerializerOptions options = jsonTypeInfo.Options;
JsonWriterOptions writerOptions = options.GetWriterOptions();
using (var bufferWriter = new PooledByteBufferWriter(options.DefaultBufferSize))
using (var writer = new Utf8JsonWriter(bufferWriter, writerOptions))
{
- WriteStack state = default;
- jsonTypeInfo.EnsureConfigured();
- JsonConverter converter = state.Initialize(jsonTypeInfo, supportContinuation: true, supportAsync: false);
+ WriteStack state = new WriteStack
+ {
+ SupportContinuation = true
+ };
+
+ jsonTypeInfo = ResolvePolymorphicTypeInfo(value, jsonTypeInfo, out state.IsPolymorphicRootValue);
+ state.Initialize(jsonTypeInfo);
bool isFinalBlock;
{
state.FlushThreshold = (int)(bufferWriter.Capacity * FlushThreshold);
- isFinalBlock = WriteCore(converter, writer, value, options, ref state);
+ isFinalBlock = WriteCore(writer, value, jsonTypeInfo, ref state);
bufferWriter.WriteToStream(utf8Json);
bufferWriter.Clear();
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
[RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)]
public static string Serialize<TValue>(TValue value, JsonSerializerOptions? options = null)
{
- Type runtimeType = GetRuntimeType(value);
- JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType);
- return WriteStringUsingSerializer(value, jsonTypeInfo);
+ JsonTypeInfo<TValue> jsonTypeInfo = GetTypeInfo<TValue>(options);
+ return WriteString(value, jsonTypeInfo);
}
/// <summary>
Type inputType,
JsonSerializerOptions? options = null)
{
- Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType);
- JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType);
- return WriteStringUsingSerializer(value, jsonTypeInfo);
+ ValidateInputType(value, inputType);
+ JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, inputType);
+ return WriteStringAsObject(value, jsonTypeInfo);
}
/// <summary>
/// </remarks>
public static string Serialize<TValue>(TValue value, JsonTypeInfo<TValue> jsonTypeInfo)
{
- return WriteStringUsingGeneratedSerializer(value, jsonTypeInfo);
+ if (jsonTypeInfo is null)
+ {
+ ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
+ }
+
+ jsonTypeInfo.EnsureConfigured();
+ return WriteString(value, jsonTypeInfo);
}
/// <summary>
ThrowHelper.ThrowArgumentNullException(nameof(context));
}
- Type type = GetRuntimeTypeAndValidateInputType(value, inputType);
- JsonTypeInfo jsonTypeInfo = GetTypeInfo(context, type);
- return WriteStringUsingGeneratedSerializer(value, jsonTypeInfo);
+ ValidateInputType(value, inputType);
+ JsonTypeInfo jsonTypeInfo = GetTypeInfo(context, inputType);
+ return WriteStringAsObject(value, jsonTypeInfo);
}
- private static string WriteStringUsingGeneratedSerializer<TValue>(in TValue value, JsonTypeInfo jsonTypeInfo)
+ private static string WriteString<TValue>(in TValue value, JsonTypeInfo<TValue> jsonTypeInfo)
{
- if (jsonTypeInfo is null)
- {
- ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
- }
+ Debug.Assert(jsonTypeInfo.IsConfigured);
JsonSerializerOptions options = jsonTypeInfo.Options;
- using (var output = new PooledByteBufferWriter(options.DefaultBufferSize))
- {
- using (var writer = new Utf8JsonWriter(output, options.GetWriterOptions()))
- {
- WriteUsingGeneratedSerializer(writer, value, jsonTypeInfo);
- }
+ using var output = new PooledByteBufferWriter(options.DefaultBufferSize);
+ using var writer = new Utf8JsonWriter(output, options.GetWriterOptions());
- return JsonReaderHelper.TranscodeHelper(output.WrittenMemory.Span);
- }
+ WriteCore(writer, value, jsonTypeInfo);
+ return JsonReaderHelper.TranscodeHelper(output.WrittenMemory.Span);
}
- private static string WriteStringUsingSerializer<TValue>(in TValue value, JsonTypeInfo jsonTypeInfo)
+ private static string WriteStringAsObject(object? value, JsonTypeInfo jsonTypeInfo)
{
- if (jsonTypeInfo is null)
- {
- ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
- }
+ Debug.Assert(jsonTypeInfo.IsConfigured);
JsonSerializerOptions options = jsonTypeInfo.Options;
- using (var output = new PooledByteBufferWriter(options.DefaultBufferSize))
- {
- using (var writer = new Utf8JsonWriter(output, options.GetWriterOptions()))
- {
- WriteUsingSerializer(writer, value, jsonTypeInfo);
- }
+ using var output = new PooledByteBufferWriter(options.DefaultBufferSize);
+ using var writer = new Utf8JsonWriter(output, options.GetWriterOptions());
- return JsonReaderHelper.TranscodeHelper(output.WrittenMemory.Span);
- }
+ WriteCoreAsObject(writer, value, jsonTypeInfo);
+ return JsonReaderHelper.TranscodeHelper(output.WrittenMemory.Span);
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
ThrowHelper.ThrowArgumentNullException(nameof(writer));
}
- Type runtimeType = GetRuntimeType(value);
- JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType);
- WriteUsingSerializer(writer, value, jsonTypeInfo);
+ JsonTypeInfo<TValue> jsonTypeInfo = GetTypeInfo<TValue>(options);
+ WriteCore(writer, value, jsonTypeInfo);
}
/// <summary>
ThrowHelper.ThrowArgumentNullException(nameof(writer));
}
- Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType);
- JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType);
- WriteUsingSerializer(writer, value, jsonTypeInfo);
+ ValidateInputType(value, inputType);
+ JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, inputType);
+ WriteCoreAsObject(writer, value, jsonTypeInfo);
}
/// <summary>
ThrowHelper.ThrowArgumentNullException(nameof(jsonTypeInfo));
}
- WriteUsingGeneratedSerializer(writer, value, jsonTypeInfo);
+ jsonTypeInfo.EnsureConfigured();
+ WriteCore(writer, value, jsonTypeInfo);
}
/// <summary>
ThrowHelper.ThrowArgumentNullException(nameof(context));
}
- Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType);
- WriteUsingGeneratedSerializer(writer, value, GetTypeInfo(context, runtimeType));
+ ValidateInputType(value, inputType);
+ JsonTypeInfo jsonTypeInfo = GetTypeInfo(context, inputType);
+ WriteCoreAsObject(writer, value, jsonTypeInfo);
}
}
}
if (jsonTypeInfo?.Type != type)
{
- jsonTypeInfo = GetTypeInfoInternal(type);
- _lastTypeInfo = jsonTypeInfo;
+ _lastTypeInfo = jsonTypeInfo = GetTypeInfoInternal(type);
}
return jsonTypeInfo;
}
+ // Caches the resolved JsonTypeInfo<object> for faster access during root-level object type serialization.
+ internal JsonTypeInfo ObjectTypeInfo
+ {
+ get
+ {
+ Debug.Assert(IsImmutable);
+ return _objectTypeInfo ??= GetTypeInfoInternal(JsonTypeInfo.ObjectType);
+ }
+ }
+
+ private JsonTypeInfo? _objectTypeInfo;
+
internal void ClearCaches()
{
_cachingContext?.Clear();
_lastTypeInfo = null;
+ _objectTypeInfo = null;
}
private CachingContext? GetCachingContext()
DefaultJsonTypeInfoResolver defaultResolver = DefaultJsonTypeInfoResolver.RootDefaultInstance();
_typeInfoResolver ??= defaultResolver;
IsImmutable = true;
+ _isInitializedForReflectionSerializer = true;
}
+ internal bool IsInitializedForReflectionSerializer => _isInitializedForReflectionSerializer;
+ private volatile bool _isInitializedForReflectionSerializer;
+
internal void InitializeForMetadataGeneration()
{
if (_typeInfoResolver is null)
// If enumerable or dictionary, the JsonTypeInfo for the element type.
private JsonTypeInfo? _elementTypeInfo;
- // Avoids having to perform an expensive cast to JsonTypeInfo<T> to check if there is a Serialize method.
- internal bool HasSerializeHandler { get; private protected set; }
+ // Flag indicating that JsonTypeInfo<T>.SerializeHandler is populated and is compatible with the associated Options instance.
+ internal bool CanUseSerializeHandler { get; private protected set; }
// Configure would normally have thrown why initializing properties for source gen but type had SerializeHandler
// so it is allowed to be used for fast-path serialization but it will throw if used for metadata-based serialization
PropertyInfoForTypeInfo.EnsureChildOf(this);
PropertyInfoForTypeInfo.EnsureConfigured();
+ CanUseSerializeHandler &= Options.SerializerContext?.CanUseSerializationLogic == true;
+
JsonConverter converter = Converter;
Debug.Assert(PropertyInfoForTypeInfo.ConverterStrategy == Converter.ConverterStrategy,
$"ConverterStrategy from PropertyInfoForTypeInfo.ConverterStrategy ({PropertyInfoForTypeInfo.ConverterStrategy}) does not match converter's ({Converter.ConverterStrategy})");
private Func<T>? _typedCreateObject;
+ internal JsonConverter<T> EffectiveConverter { get; }
+
/// <summary>
/// Gets or sets a parameterless factory to be used on deserialization.
/// </summary>
internal JsonTypeInfo(JsonConverter converter, JsonSerializerOptions options)
: base(typeof(T), converter, options)
- { }
+ {
+ EffectiveConverter = converter is JsonConverter<T> jsonConverter ? jsonConverter : converter.CreateCastingConverter<T>();
+ }
/// <summary>
/// Serializes an instance of <typeparamref name="T"/> using
{
Debug.Assert(!IsConfigured, "We should not mutate configured JsonTypeInfo");
_serialize = value;
- HasSerializeHandler = value != null;
+ CanUseSerializeHandler = value != null;
}
}
}
}
- public void Initialize(Type type, JsonSerializerOptions options, bool supportContinuation)
- {
- JsonTypeInfo jsonTypeInfo = options.GetTypeInfoForRootType(type);
- Initialize(jsonTypeInfo, supportContinuation);
- }
-
- internal void Initialize(JsonTypeInfo jsonTypeInfo, bool supportContinuation = false)
+ internal void Initialize(JsonTypeInfo jsonTypeInfo)
{
JsonSerializerOptions options = jsonTypeInfo.Options;
if (options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve)
PreserveReferences = true;
}
- SupportContinuation = supportContinuation;
Current.JsonTypeInfo = jsonTypeInfo;
Current.JsonPropertyInfo = jsonTypeInfo.PropertyInfoForTypeInfo;
Current.NumberHandling = Current.JsonPropertyInfo.EffectiveNumberHandling;
/// </summary>
public bool IsContinuation => _continuationCount != 0;
+ /// <summary>
+ /// Indicates that the root-level JsonTypeInfo is the result of
+ /// polymorphic dispatch from the internal System.Object converter.
+ /// </summary>
+ public bool IsPolymorphicRootValue;
+
// The bag of preservable references.
public ReferenceResolver ReferenceResolver;
}
}
- /// <summary>
- /// Initialize the state without delayed initialization of the JsonTypeInfo.
- /// </summary>
- public JsonConverter Initialize(Type type, JsonSerializerOptions options, bool supportContinuation, bool supportAsync)
- {
- JsonTypeInfo jsonTypeInfo = options.GetTypeInfoForRootType(type);
- return Initialize(jsonTypeInfo, supportContinuation, supportAsync);
- }
-
- internal JsonConverter Initialize(JsonTypeInfo jsonTypeInfo, bool supportContinuation, bool supportAsync)
+ internal void Initialize(JsonTypeInfo jsonTypeInfo)
{
- Debug.Assert(!supportAsync || supportContinuation, "supportAsync implies supportContinuation.");
+ Debug.Assert(!IsContinuation);
+ Debug.Assert(CurrentDepth == 0);
Current.JsonTypeInfo = jsonTypeInfo;
Current.JsonPropertyInfo = jsonTypeInfo.PropertyInfoForTypeInfo;
Debug.Assert(options.ReferenceHandler != null);
ReferenceResolver = options.ReferenceHandler.CreateResolver(writing: true);
}
-
- SupportContinuation = supportContinuation;
- SupportAsync = supportAsync;
-
- return jsonTypeInfo.Converter;
}
/// <summary>
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Collections.Generic;
-using System.Dynamic;
using System.Globalization;
using System.Tests;
using System.Text.Json.Serialization.Samples;
value = dynamicType.Value;
}
- JsonSerializer.Serialize<object>(writer, value, options);
+ JsonSerializer.Serialize(writer, value, value.GetType(), options);
}
private void ReadList(JsonDynamicArray dynamicArray, ref Utf8JsonReader reader, JsonSerializerOptions options)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Collections.Generic;
+using System.Text.Json.Serialization.Metadata;
using Xunit;
namespace System.Text.Json.Serialization.Tests
public static partial class CustomConverterTests
{
/// <summary>
- /// A converter that uses Object as it's type.
+ /// A converter that uses Object as its type.
/// </summary>
private class ObjectToCustomerOrIntConverter : JsonConverter<object>
{
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
{
- Assert.IsType<object>(value);
- writer.WriteStartObject();
- writer.WriteEndObject();
+ if (value is null)
+ {
+ writer.WriteNullValue();
+ }
+ else if (value.GetType() == typeof(object))
+ {
+ writer.WriteStartObject();
+ writer.WriteEndObject();
+ }
+ else
+ {
+ JsonSerializer.Serialize(writer, value, value.GetType(), options);
+ }
}
}
Assert.Equal(expectedJson, actualJson);
}
+ [Theory]
+ [InlineData(-2)]
+ [InlineData(false)]
+ [InlineData("string")]
+ [InlineData(3.1415926)]
+ public static void CustomSystemObjectConverter_DoesNotUsePolymorphismInAllContexts(object value)
+ {
+ // Regression test for https://github.com/dotnet/runtime/issues/72681
+
+ var options = new JsonSerializerOptions { Converters = { new CustomSystemObjectConverter() } };
+
+ TestValue(value, "42");
+ TestValue(new { Value = value }, """{"Value":42}""");
+ TestValue(new object[] { value }, "[42]");
+ TestValue(new Dictionary<string, object> { ["key"] = value }, """{"key":42}""");
+
+ void TestValue<T>(T value, string expectedJson)
+ {
+ string json = JsonSerializer.Serialize(value, options);
+ Assert.Equal(expectedJson, json);
+
+ JsonTypeInfo<T> jsonTypeInfo = (JsonTypeInfo<T>)options.GetTypeInfo(typeof(T));
+ json = JsonSerializer.Serialize(value, jsonTypeInfo);
+ Assert.Equal(expectedJson, json);
+ }
+ }
+
private class CustomSystemObjectConverter : JsonConverter<object>
{
public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => throw new NotImplementedException();
}
[Theory]
- [ActiveIssue("https://github.com/dotnet/runtime/issues/72187")]
[MemberData(nameof(PolymorphicClass_WithBaseTypeDiscriminator.GetTestData), MemberType = typeof(PolymorphicClass_WithBaseTypeDiscriminator))]
public async Task PolymorphicClass_BoxedSerialization_DoesNotUseTypeDiscriminators(PolymorphicClass_WithBaseTypeDiscriminator value, string expectedJson)
{
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
+using System.Text.Json.Serialization.Metadata;
using System.Threading.Tasks;
using Xunit;
{
}
- [Fact]
- public async Task PrimitivesAsRootObject()
+ [Theory]
+ [InlineData(1, "1")]
+ [InlineData("stringValue", @"""stringValue""")]
+ [InlineData(true, "true")]
+ [InlineData(null, "null")]
+ [InlineData(new int[] { 1, 2, 3}, "[1,2,3]")]
+ public async Task PrimitivesAsRootObject(object? value, string expectedJson)
{
- string json = await Serializer.SerializeWrapper<object>(1);
- Assert.Equal("1", json);
- json = await Serializer.SerializeWrapper(1, typeof(object));
- Assert.Equal("1", json);
-
- json = await Serializer.SerializeWrapper<object>("foo");
- Assert.Equal(@"""foo""", json);
- json = await Serializer.SerializeWrapper("foo", typeof(object));
- Assert.Equal(@"""foo""", json);
-
- json = await Serializer.SerializeWrapper<object>(true);
- Assert.Equal(@"true", json);
- json = await Serializer.SerializeWrapper(true, typeof(object));
- Assert.Equal(@"true", json);
-
- json = await Serializer.SerializeWrapper<object>(null);
- Assert.Equal(@"null", json);
- json = await Serializer.SerializeWrapper((object)null, typeof(object));
- Assert.Equal(@"null", json);
-
- decimal pi = 3.1415926535897932384626433833m;
- json = await Serializer.SerializeWrapper<object>(pi);
- Assert.Equal(@"3.1415926535897932384626433833", json);
- json = await Serializer.SerializeWrapper(pi, typeof(object));
- Assert.Equal(@"3.1415926535897932384626433833", json);
+ string json = await Serializer.SerializeWrapper(value);
+ Assert.Equal(expectedJson, json);
+ json = await Serializer.SerializeWrapper(value, typeof(object));
+ Assert.Equal(expectedJson, json);
+
+ var options = new JsonSerializerOptions { TypeInfoResolver = new DefaultJsonTypeInfoResolver() };
+ JsonTypeInfo<object> objectTypeInfo = (JsonTypeInfo<object>)options.GetTypeInfo(typeof(object));
+ json = await Serializer.SerializeWrapper(value, objectTypeInfo);
+ Assert.Equal(expectedJson, json);
}
[Fact]