Skip to content

Commit

Permalink
Fix perf regression by using ReferenceHandlerStrategy field in JsonSe…
Browse files Browse the repository at this point in the history
…rializerOptions
  • Loading branch information
jozkee committed Jan 26, 2021
1 parent 2e55d73 commit 1905462
Show file tree
Hide file tree
Showing 15 changed files with 51 additions and 37 deletions.
1 change: 1 addition & 0 deletions src/libraries/System.Text.Json/src/System.Text.Json.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@
<Compile Include="System\Text\Json\Serialization\ReadStackFrame.cs" />
<Compile Include="System\Text\Json\Serialization\ReferenceHandler.cs" />
<Compile Include="System\Text\Json\Serialization\ReferenceHandlerOfT.cs" />
<Compile Include="System\Text\Json\Serialization\ReferenceHandlingStrategy.cs" />
<Compile Include="System\Text\Json\Serialization\ReferenceResolver.cs" />
<Compile Include="System\Text\Json\Serialization\ReflectionEmitMemberAccessor.cs" />
<Compile Include="System\Text\Json\Serialization\ReflectionMemberAccessor.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ internal sealed override bool OnTryRead(
}

// Handle the metadata properties.
bool preserveReferences = JsonSerializer.IsPreserveReferencesEnabled(options);
bool preserveReferences = options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve;
if (preserveReferences && state.Current.ObjectState < StackFrameObjectState.PropertyValue)
{
if (JsonSerializer.ResolveMetadataForJsonObject<TCollection>(ref reader, ref state, options))
Expand Down Expand Up @@ -272,7 +272,7 @@ internal sealed override bool OnTryWrite(
{
state.Current.ProcessedStartToken = true;
writer.WriteStartObject();
if (JsonSerializer.IsPreserveReferencesEnabled(options))
if (options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve)
{
if (JsonSerializer.WriteReferenceForObject(this, dictionary, ref state, writer) == MetadataPropertyName.Ref)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ internal override bool OnTryRead(
{
// Slower path that supports continuation and preserved references.

bool preserveReferences = JsonSerializer.IsPreserveReferencesEnabled(options);
bool preserveReferences = options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve;
if (state.Current.ObjectState == StackFrameObjectState.None)
{
if (reader.TokenType == JsonTokenType.StartArray)
Expand Down Expand Up @@ -236,7 +236,7 @@ internal sealed override bool OnTryWrite(
if (!state.Current.ProcessedStartToken)
{
state.Current.ProcessedStartToken = true;
if (JsonSerializer.IsPreserveReferencesEnabled(options))
if (options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve)
{
MetadataPropertyName metadata = JsonSerializer.WriteReferenceForCollection(this, value, ref state, writer);
if (metadata == MetadataPropertyName.Ref)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert,
// Handle the metadata properties.
if (state.Current.ObjectState < StackFrameObjectState.PropertyValue)
{
if (JsonSerializer.IsPreserveReferencesEnabled(options))
if (options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve)
{
if (JsonSerializer.ResolveMetadataForJsonObject<T>(ref reader, ref state, options))
{
Expand Down Expand Up @@ -233,7 +233,7 @@ internal sealed override bool OnTryWrite(
if (!state.SupportContinuation)
{
writer.WriteStartObject();
if (JsonSerializer.IsPreserveReferencesEnabled(options))
if (options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve)
{
if (JsonSerializer.WriteReferenceForObject(this, objectValue, ref state, writer) == MetadataPropertyName.Ref)
{
Expand Down Expand Up @@ -288,7 +288,7 @@ internal sealed override bool OnTryWrite(
if (!state.Current.ProcessedStartToken)
{
writer.WriteStartObject();
if (JsonSerializer.IsPreserveReferencesEnabled(options))
if (options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve)
{
if (JsonSerializer.WriteReferenceForObject(this, objectValue, ref state, writer) == MetadataPropertyName.Ref)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace System.Text.Json.Serialization
{
internal sealed class IgnoreReferenceHandler : ReferenceHandler
{
public IgnoreReferenceHandler() => UsePreserveSemantics = false;
public IgnoreReferenceHandler() => HandlingStrategy = ReferenceHandlingStrategy.IgnoreCycle;

public override ReferenceResolver CreateResolver() => new IgnoreReferenceResolver();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ internal sealed override bool WriteCoreAsObject(
}

// Root object is a boxed value type, we need to push it to the reference stack before it gets unboxed here.
if (value != null && JsonSerializer.IsIgnoreCyclesEnabled(options))
if (options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.IgnoreCycle && value != null)
{
state.ReferenceResolver.PushReferenceForCycleDetection(value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,8 @@ internal bool TryRead(ref Utf8JsonReader reader, Type typeToConvert, JsonSeriali
ref reader);
}

if (CanBePolymorphic && JsonSerializer.IsPreserveReferencesEnabled(options) && value is JsonElement element)
if (options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve &&
CanBePolymorphic && value is JsonElement element)
{
// Edge case where we want to lookup for a reference when parsing into typeof(object)
// instead of return `value` as a JsonElement.
Expand Down Expand Up @@ -303,20 +304,17 @@ internal bool TryWrite(Utf8JsonWriter writer, in T value, JsonSerializerOptions
ThrowHelper.ThrowJsonException_SerializerCycleDetected(options.EffectiveMaxDepth);
}

bool ignoreCyclesEnabled = JsonSerializer.IsIgnoreCyclesEnabled(options);
bool ignoreCyclesPopReference = false;

if (CanBeNull && IsNull(value))
if (CanBeNull && !HandleNullOnWrite && IsNull(value))
{
if (!HandleNullOnWrite)
{
// We do not pass null values to converters unless HandleNullOnWrite is true. Null values for properties were
// already handled in GetMemberAndWriteJson() so we don't need to check for IgnoreNullValues here.
writer.WriteNullValue();
return true;
}
// We do not pass null values to converters unless HandleNullOnWrite is true. Null values for properties were
// already handled in GetMemberAndWriteJson() so we don't need to check for IgnoreNullValues here.
writer.WriteNullValue();
return true;
}
else if (ignoreCyclesEnabled && !IsValueType)

bool ignoreCyclesPopReference = false;
if (options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.IgnoreCycle &&
!IsValueType && !IsNull(value))
{
ReferenceResolver resolver = state.ReferenceResolver;
// Write null to break reference cycles.
Expand Down Expand Up @@ -363,9 +361,10 @@ internal bool TryWrite(Utf8JsonWriter writer, in T value, JsonSerializerOptions
JsonConverter jsonConverter = state.Current.InitializeReEntry(type, options);
if (jsonConverter != this)
{
if (ignoreCyclesEnabled && jsonConverter.IsValueType)
if (options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.IgnoreCycle &&
jsonConverter.IsValueType)
{
// For boxed value types: push the value before it gets unboxed.
// For boxed value types: push the value before it gets unboxed on TryWriteAsObject.
state.ReferenceResolver.PushReferenceForCycleDetection(value);
ignoreCyclesPopReference = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,9 @@ public override bool GetMemberAndWriteJson(object obj, ref WriteStack state, Utf
{
T value = Get!(obj);

if (JsonSerializer.IsIgnoreCyclesEnabled(Options) && !Converter.IsValueType &&
value != null && state.ReferenceResolver.ContainsReferenceForCycleDetection(value))
if (Options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.IgnoreCycle &&
!Converter.IsValueType && value != null &&
state.ReferenceResolver.ContainsReferenceForCycleDetection(value))
{
// If a reference cycle is detected, treat value as null.
value = default!;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ internal static ReadOnlySpan<byte> GetPropertyName(
unescapedPropertyName = propertyName;
}

if (JsonSerializer.IsPreserveReferencesEnabled(options))
if (options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve)
{
if (propertyName.Length > 0 && propertyName[0] == '$')
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,5 @@ internal static MetadataPropertyName WriteReferenceForCollection(

return writtenMetadataName;
}

internal static bool IsPreserveReferencesEnabled(JsonSerializerOptions options)
=> options.ReferenceHandler?.UsePreserveSemantics ?? false;

internal static bool IsIgnoreCyclesEnabled(JsonSerializerOptions options)
=> !options.ReferenceHandler?.UsePreserveSemantics ?? false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ public JsonSerializerOptions(JsonSerializerOptions options)

Converters = new ConverterList(this, (ConverterList)options.Converters);
EffectiveMaxDepth = options.EffectiveMaxDepth;
ReferenceHandlingStrategy = options.ReferenceHandlingStrategy;

// _classes is not copied as sharing the JsonClassInfo and JsonPropertyInfo caches can result in
// unnecessary references to type metadata, potentially hindering garbage collection on the source options.
Expand Down Expand Up @@ -487,9 +488,13 @@ public ReferenceHandler? ReferenceHandler
{
VerifyMutable();
_referenceHandler = value;
ReferenceHandlingStrategy = value?.HandlingStrategy ?? ReferenceHandlingStrategy.None;
}
}

// The cached value used to determine if ReferenceHandler should use Preserve or IgnoreCycle semanitcs or None of them.
internal ReferenceHandlingStrategy ReferenceHandlingStrategy = ReferenceHandlingStrategy.None;

internal MemberAccessor MemberAccessorStrategy
{
get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public void Initialize(Type type, JsonSerializerOptions options, bool supportCon

Current.NumberHandling = Current.JsonPropertyInfo.NumberHandling;

bool preserveReferences = JsonSerializer.IsPreserveReferencesEnabled(options);
bool preserveReferences = options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve;
if (preserveReferences)
{
ReferenceResolver = options.ReferenceHandler!.CreateResolver(writing: false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ namespace System.Text.Json.Serialization
public abstract class ReferenceHandler
{
/// <summary>
/// Whether we read and write preserve references or we ignore references when writing.
/// One of the enumeration values that indicates whether this ReferenceHandler implementation should use Preserve semantics or IgnoreCycle semantics.
/// The defualt is Preserve.
/// </summary>
internal bool UsePreserveSemantics = true;
internal ReferenceHandlingStrategy HandlingStrategy = ReferenceHandlingStrategy.Preserve;

/// <summary>
/// Metadata properties will be honored when deserializing JSON objects and arrays into reference types and written when serializing reference types. This is necessary to create round-trippable JSON from objects that contain cycles or duplicate references.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Text.Json.Serialization
{
internal enum ReferenceHandlingStrategy
{
None,
Preserve,
IgnoreCycle
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,9 @@ public JsonConverter Initialize(Type type, JsonSerializerOptions options, bool s
Current.DeclaredJsonPropertyInfo = jsonClassInfo.PropertyInfoForClassInfo;
Current.NumberHandling = Current.DeclaredJsonPropertyInfo.NumberHandling;

if (options.ReferenceHandler != null)
if (options.ReferenceHandlingStrategy != ReferenceHandlingStrategy.None)
{
Debug.Assert(options.ReferenceHandler != null);
ReferenceResolver = options.ReferenceHandler.CreateResolver(writing: true);
}

Expand Down

0 comments on commit 1905462

Please sign in to comment.