Skip to content

Commit

Permalink
Implement a heuristic for using fast-path serialization in streaming …
Browse files Browse the repository at this point in the history
…JsonSerializer methods. (#78646)

* Implement a heuristic for using fast-path serialization in streaming JsonSerializer methods.

* Add testing for polymorphic serialization

* Apply refactorings to root-level deserialization methods similar to serialization for consistency.

* Use pooled Utf8JsonWriter in fast-path async serialization

* Only record streaming serialization sizes on successful operations
  • Loading branch information
eiriktsarpalis authored Dec 5, 2022
1 parent a59eae5 commit 3559d33
Show file tree
Hide file tree
Showing 31 changed files with 911 additions and 607 deletions.
4 changes: 2 additions & 2 deletions src/libraries/System.Text.Json/src/System.Text.Json.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ The System.Text.Json library is built-in as part of the shared framework in .NET
<Compile Include="System\Text\Json\Serialization\Metadata\IJsonTypeInfoResolver.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonDerivedType.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonPolymorphismOptions.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonTypeInfoOfT.ReadHelper.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonTypeInfoOfT.WriteHelpers.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonTypeInfoResolver.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\JsonTypeInfoKind.cs" />
<Compile Include="System\Text\Json\Serialization\Metadata\CustomJsonTypeInfoOfT.cs" />
Expand Down Expand Up @@ -226,14 +228,12 @@ The System.Text.Json library is built-in as part of the shared framework in .NET
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Helpers.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.HandleMetadata.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.HandlePropertyName.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.Helpers.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.Span.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.Stream.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.String.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Read.Utf8JsonReader.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.ByteArray.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.HandleMetadata.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.Helpers.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.Stream.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.String.cs" />
<Compile Include="System\Text\Json\Serialization\JsonSerializer.Write.Utf8JsonWriter.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,6 @@ internal virtual JsonTypeInfo CreateCustomJsonTypeInfo(JsonSerializerOptions opt
/// </summary>
internal bool IsInternalConverterForNumberType { get; init; }

/// <summary>
/// Loosely-typed ReadCore() that forwards to strongly-typed ReadCore().
/// </summary>
internal abstract object? ReadCoreAsObject(ref Utf8JsonReader reader, JsonSerializerOptions options, scoped ref ReadStack state);


internal static bool ShouldFlush(Utf8JsonWriter writer, ref WriteStack state)
{
// If surpassed flush threshold then return false which will flush stream.
Expand All @@ -149,11 +143,6 @@ internal static bool ShouldFlush(Utf8JsonWriter writer, ref WriteStack state)

internal abstract bool TryWriteAsObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options, ref WriteStack state);

/// <summary>
/// Loosely-typed WriteCore() that forwards to strongly-typed WriteCore().
/// </summary>
internal abstract bool WriteCoreAsObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options, ref WriteStack state);

/// <summary>
/// Loosely-typed WriteToPropertyName() that forwards to strongly-typed WriteToPropertyName().
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,6 @@ internal JsonConverter GetConverterInternal(Type typeToConvert, JsonSerializerOp
return converter;
}

internal sealed override object ReadCoreAsObject(
ref Utf8JsonReader reader,
JsonSerializerOptions options,
scoped ref ReadStack state)
{
Debug.Fail("We should never get here.");

throw new InvalidOperationException();
}

internal sealed override bool OnTryReadAsObject(
ref Utf8JsonReader reader,
JsonSerializerOptions options,
Expand Down Expand Up @@ -106,17 +96,6 @@ internal sealed override bool TryWriteAsObject(

internal sealed override Type TypeToConvert => null!;

internal sealed override bool WriteCoreAsObject(
Utf8JsonWriter writer,
object? value,
JsonSerializerOptions options,
ref WriteStack state)
{
Debug.Fail("We should never get here.");

throw new InvalidOperationException();
}

internal sealed override void WriteAsPropertyNameCoreAsObject(
Utf8JsonWriter writer, object value,
JsonSerializerOptions options,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,10 @@ namespace System.Text.Json.Serialization
{
public partial class JsonConverter<T>
{
internal sealed override object? ReadCoreAsObject(
ref Utf8JsonReader reader,
JsonSerializerOptions options,
scoped ref ReadStack state)
{
return ReadCore(ref reader, options, ref state);
}

internal T? ReadCore(
ref Utf8JsonReader reader,
JsonSerializerOptions options,
scoped ref ReadStack state)
ref ReadStack state)
{
try
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,6 @@ namespace System.Text.Json.Serialization
{
public partial class JsonConverter<T>
{
internal sealed override bool WriteCoreAsObject(
Utf8JsonWriter writer,
object? value,
JsonSerializerOptions options,
ref WriteStack state)
{
if (
#if NETCOREAPP
// 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 (default(T) is not null && value is null)
{
ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(TypeToConvert);
}

// Root object is a boxed value type, we need to push it to the reference stack before it gets unboxed here.
if (options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.IgnoreCycles && value != null)
{
state.ReferenceResolver.PushReferenceForCycleDetection(value);
}
}

T actualValue = (T)value!;
return WriteCore(writer, actualValue, options, ref state);
}

internal bool WriteCore(
Utf8JsonWriter writer,
in T value,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,23 @@ private static JsonTypeInfo GetTypeInfo(JsonSerializerContext context, Type inpu
return info;
}

private static void ValidateInputType(object? value, Type inputType)
{
if (inputType is null)
{
ThrowHelper.ThrowArgumentNullException(nameof(inputType));
}

if (value is not null)
{
Type runtimeType = value.GetType();
if (!inputType.IsAssignableFrom(runtimeType))
{
ThrowHelper.ThrowArgumentException_DeserializeWrongType(inputType, value);
}
}
}

internal static bool IsValidNumberHandlingValue(JsonNumberHandling handling) =>
JsonHelpers.IsInRangeInclusive((int)handling, 0,
(int)(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ public static partial class JsonSerializer
ThrowHelper.ThrowArgumentNullException(nameof(document));
}

JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, typeof(TValue));
return ReadDocument<TValue>(document, jsonTypeInfo);
JsonTypeInfo<TValue> jsonTypeInfo = GetTypeInfo<TValue>(options);
ReadOnlySpan<byte> utf8Json = document.GetRootRawValue().Span;
return ReadFromSpan(utf8Json, jsonTypeInfo);
}

/// <summary>
Expand Down Expand Up @@ -70,7 +71,8 @@ public static partial class JsonSerializer
}

JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, returnType);
return ReadDocument<object?>(document, jsonTypeInfo);
ReadOnlySpan<byte> utf8Json = document.GetRootRawValue().Span;
return ReadFromSpanAsObject(utf8Json, jsonTypeInfo);
}

/// <summary>
Expand Down Expand Up @@ -106,7 +108,8 @@ public static partial class JsonSerializer
}

jsonTypeInfo.EnsureConfigured();
return ReadDocument<TValue>(document, jsonTypeInfo);
ReadOnlySpan<byte> utf8Json = document.GetRootRawValue().Span;
return ReadFromSpan(utf8Json, jsonTypeInfo);
}

/// <summary>
Expand Down Expand Up @@ -161,13 +164,8 @@ public static partial class JsonSerializer
}

JsonTypeInfo jsonTypeInfo = GetTypeInfo(context, returnType);
return ReadDocument<object?>(document, jsonTypeInfo);
}

private static TValue? ReadDocument<TValue>(JsonDocument document, JsonTypeInfo jsonTypeInfo)
{
ReadOnlySpan<byte> utf8Json = document.GetRootRawValue().Span;
return ReadFromSpan<TValue>(utf8Json, jsonTypeInfo);
return ReadFromSpanAsObject(utf8Json, jsonTypeInfo);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ public static partial class JsonSerializer
[RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)]
public static TValue? Deserialize<TValue>(this JsonElement element, JsonSerializerOptions? options = null)
{
JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, typeof(TValue));
return ReadUsingMetadata<TValue>(element, jsonTypeInfo);
JsonTypeInfo<TValue> jsonTypeInfo = GetTypeInfo<TValue>(options);
ReadOnlySpan<byte> utf8Json = element.GetRawValue().Span;
return ReadFromSpan(utf8Json, jsonTypeInfo);
}

/// <summary>
Expand Down Expand Up @@ -58,7 +59,8 @@ public static partial class JsonSerializer
}

JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, returnType);
return ReadUsingMetadata<object?>(element, jsonTypeInfo);
ReadOnlySpan<byte> utf8Json = element.GetRawValue().Span;
return ReadFromSpanAsObject(utf8Json, jsonTypeInfo);
}

/// <summary>
Expand Down Expand Up @@ -86,7 +88,8 @@ public static partial class JsonSerializer
}

jsonTypeInfo.EnsureConfigured();
return ReadUsingMetadata<TValue>(element, jsonTypeInfo);
ReadOnlySpan<byte> utf8Json = element.GetRawValue().Span;
return ReadFromSpan(utf8Json, jsonTypeInfo);
}

/// <summary>
Expand Down Expand Up @@ -133,13 +136,8 @@ public static partial class JsonSerializer
}

JsonTypeInfo jsonTypeInfo = GetTypeInfo(context, returnType);
return ReadUsingMetadata<object?>(element, jsonTypeInfo);
}

private static TValue? ReadUsingMetadata<TValue>(JsonElement element, JsonTypeInfo jsonTypeInfo)
{
ReadOnlySpan<byte> utf8Json = element.GetRawValue().Span;
return ReadFromSpan<TValue>(utf8Json, jsonTypeInfo);
return ReadFromSpanAsObject(utf8Json, jsonTypeInfo);
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ public static partial class JsonSerializer
[RequiresDynamicCode(SerializationRequiresDynamicCodeMessage)]
public static TValue? Deserialize<TValue>(this JsonNode? node, JsonSerializerOptions? options = null)
{
JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, typeof(TValue));
return ReadNode<TValue>(node, jsonTypeInfo);
JsonTypeInfo<TValue> jsonTypeInfo = GetTypeInfo<TValue>(options);
return ReadFromNode(node, jsonTypeInfo);
}

/// <summary>
Expand All @@ -57,7 +57,7 @@ public static partial class JsonSerializer
}

JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, returnType);
return ReadNode<object?>(node, jsonTypeInfo);
return ReadFromNodeAsObject(node, jsonTypeInfo);
}

/// <summary>
Expand Down Expand Up @@ -85,7 +85,7 @@ public static partial class JsonSerializer
}

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

/// <summary>
Expand Down Expand Up @@ -132,10 +132,10 @@ public static partial class JsonSerializer
}

JsonTypeInfo jsonTypeInfo = GetTypeInfo(context, returnType);
return ReadNode<object?>(node, jsonTypeInfo);
return ReadFromNodeAsObject(node, jsonTypeInfo);
}

private static TValue? ReadNode<TValue>(JsonNode? node, JsonTypeInfo jsonTypeInfo)
private static TValue? ReadFromNode<TValue>(JsonNode? node, JsonTypeInfo<TValue> jsonTypeInfo)
{
JsonSerializerOptions options = jsonTypeInfo.Options;

Expand All @@ -153,7 +153,28 @@ public static partial class JsonSerializer
}
}

return ReadFromSpan<TValue>(output.WrittenMemory.Span, jsonTypeInfo);
return ReadFromSpan(output.WrittenMemory.Span, jsonTypeInfo);
}

private static object? ReadFromNodeAsObject(JsonNode? node, JsonTypeInfo jsonTypeInfo)
{
JsonSerializerOptions options = jsonTypeInfo.Options;

// 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()))
{
if (node is null)
{
writer.WriteNullValue();
}
else
{
node.WriteTo(writer, options);
}
}

return ReadFromSpanAsObject(output.WrittenMemory.Span, jsonTypeInfo);
}
}
}
Loading

0 comments on commit 3559d33

Please sign in to comment.