Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support use of fast-path serialization in combined JsonSerializerContexts. #80741

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ private sealed partial class Emitter
private const string DefaultOptionsStaticVarName = "s_defaultOptions";
private const string DefaultContextBackingStaticVarName = "s_defaultContext";
internal const string GetConverterFromFactoryMethodName = "GetConverterFromFactory";
private const string OriginatingResolverPropertyName = "OriginatingResolver";
private const string InfoVarName = "info";
private const string PropertyInfoVarName = "propertyInfo";
internal const string JsonContextVarName = "jsonContext";
Expand Down Expand Up @@ -1160,6 +1161,9 @@ private static string GenerateForType(TypeGenerationSpec typeMetadata, string me
{typeInfoPropertyTypeRef}? {JsonTypeInfoReturnValueLocalVariableName} = null;
{WrapWithCheckForCustomConverter(metadataInitSource, typeCompilableName)}

{ /* NB OriginatingResolver should be the last property set by the source generator. */ ""}
{JsonTypeInfoReturnValueLocalVariableName}.{OriginatingResolverPropertyName} = this;

return {JsonTypeInfoReturnValueLocalVariableName};
}}
{additionalSource}";
Expand Down
2 changes: 2 additions & 0 deletions src/libraries/System.Text.Json/ref/System.Text.Json.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1245,6 +1245,8 @@ internal JsonTypeInfo() { }
public System.Action<object>? OnSerialized { get { throw null; } set { } }
public System.Action<object>? OnSerializing { get { throw null; } set { } }
public System.Text.Json.JsonSerializerOptions Options { get { throw null; } }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver? OriginatingResolver { get { throw null; } set { } }
public System.Text.Json.Serialization.Metadata.JsonPolymorphismOptions? PolymorphismOptions { get { throw null; } set { } }
public System.Collections.Generic.IList<System.Text.Json.Serialization.Metadata.JsonPropertyInfo> Properties { get { throw null; } }
public System.Type Type { get { throw null; } }
Expand Down
3 changes: 0 additions & 3 deletions src/libraries/System.Text.Json/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -252,9 +252,6 @@
<data name="TypeInfoImmutable" xml:space="preserve">
<value>This JsonTypeInfo instance is marked read-only or has already been used in serialization or deserialization.</value>
</data>
<data name="PropertyInfoImmutable" xml:space="preserve">
<value>This JsonTypeInfo instance is marked read-only or has already been used in serialization or deserialization.</value>
</data>
<data name="MaxDepthMustBePositive" xml:space="preserve">
<value>Max depth must be positive.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public void CopyTo(TItem[] array, int arrayIndex)
_list.CopyTo(array, arrayIndex);
}

public IEnumerator<TItem> GetEnumerator()
public List<TItem>.Enumerator GetEnumerator()
{
return _list.GetEnumerator();
}
Expand Down Expand Up @@ -107,6 +107,11 @@ public void RemoveAt(int index)
_list.RemoveAt(index);
}

IEnumerator<TItem> IEnumerable<TItem>.GetEnumerator()
{
return _list.GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
return _list.GetEnumerator();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ internal override bool OnTryWrite(Utf8JsonWriter writer, T value, JsonSerializer
!state.CurrentContainsMetadata) // Do not use the fast path if state needs to write metadata.
{
Debug.Assert(jsonTypeInfo is JsonTypeInfo<T> typeInfo && typeInfo.SerializeHandler != null);
Debug.Assert(jsonTypeInfo.CanUseSerializeHandler);
eiriktsarpalis marked this conversation as resolved.
Show resolved Hide resolved
((JsonTypeInfo<T>)jsonTypeInfo).SerializeHandler!(writer, value);
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,20 @@ public JsonSerializerOptions Options
/// Indicates whether pre-generated serialization logic for types in the context
/// is compatible with the run time specified <see cref="JsonSerializerOptions"/>.
/// </summary>
internal bool CanUseFastPathSerializationLogic(JsonSerializerOptions options)
internal bool IsCompatibleWithGeneratedOptions(JsonSerializerOptions options)
{
Debug.Assert(options.TypeInfoResolver == this);
Debug.Assert(options != null);

JsonSerializerOptions? generatedSerializerOptions = GeneratedSerializerOptions;

if (ReferenceEquals(options, generatedSerializerOptions))
{
// Fast path for the 99% case
return true;
}

return
GeneratedSerializerOptions is not null &&
generatedSerializerOptions is not null &&
// Guard against unsupported features
options.Converters.Count == 0 &&
options.Encoder == null &&
Expand All @@ -59,13 +67,13 @@ GeneratedSerializerOptions is not null &&
#pragma warning restore SYSLIB0020

// Ensure options values are consistent with expected defaults.
options.DefaultIgnoreCondition == GeneratedSerializerOptions.DefaultIgnoreCondition &&
options.IgnoreReadOnlyFields == GeneratedSerializerOptions.IgnoreReadOnlyFields &&
options.IgnoreReadOnlyProperties == GeneratedSerializerOptions.IgnoreReadOnlyProperties &&
options.IncludeFields == GeneratedSerializerOptions.IncludeFields &&
options.PropertyNamingPolicy == GeneratedSerializerOptions.PropertyNamingPolicy &&
options.DictionaryKeyPolicy == GeneratedSerializerOptions.DictionaryKeyPolicy &&
options.WriteIndented == GeneratedSerializerOptions.WriteIndented;
options.DefaultIgnoreCondition == generatedSerializerOptions.DefaultIgnoreCondition &&
options.IgnoreReadOnlyFields == generatedSerializerOptions.IgnoreReadOnlyFields &&
options.IgnoreReadOnlyProperties == generatedSerializerOptions.IgnoreReadOnlyProperties &&
options.IncludeFields == generatedSerializerOptions.IncludeFields &&
options.PropertyNamingPolicy == generatedSerializerOptions.PropertyNamingPolicy &&
options.DictionaryKeyPolicy == generatedSerializerOptions.DictionaryKeyPolicy &&
options.WriteIndented == generatedSerializerOptions.WriteIndented;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -605,18 +605,48 @@ public ReferenceHandler? ReferenceHandler
}
}

/// <summary>
/// Returns true if options uses compatible built-in resolvers or a combination of compatible built-in resolvers.
/// </summary>
internal bool CanUseFastPathSerializationLogic
{
get
{
Debug.Assert(IsReadOnly);
return _canUseFastPathSerializationLogic ??= _typeInfoResolver is JsonSerializerContext ctx ? ctx.CanUseFastPathSerializationLogic(this) : false;
Debug.Assert(TypeInfoResolver != null);
return _canUseFastPathSerializationLogic ??= CanUseFastPath(TypeInfoResolver);

bool CanUseFastPath(IJsonTypeInfoResolver resolver)
{
switch (resolver)
{
case DefaultJsonTypeInfoResolver defaultResolver:
return defaultResolver.GetType() == typeof(DefaultJsonTypeInfoResolver) &&
defaultResolver.Modifiers.Count == 0;
case JsonSerializerContext ctx:
return ctx.IsCompatibleWithGeneratedOptions(this);
case JsonTypeInfoResolver.CombiningJsonTypeInfoResolver combiningResolver:
foreach (IJsonTypeInfoResolver component in combiningResolver.Resolvers)
{
Debug.Assert(component is not JsonTypeInfoResolver.CombiningJsonTypeInfoResolver, "recurses at most once.");
if (!CanUseFastPath(component))
{
return false;
}
}

return true;

default:
return false;
}
}
}
}

private bool? _canUseFastPathSerializationLogic;

// The cached value used to determine if ReferenceHandler should use Preserve or IgnoreCycles semanitcs or None of them.
// The cached value used to determine if ReferenceHandler should use Preserve or IgnoreCycles semantics or None of them.
internal ReferenceHandlingStrategy ReferenceHandlingStrategy = ReferenceHandlingStrategy.None;
// Workaround https://github.com/dotnet/linker/issues/2715
[UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ public virtual JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options

JsonTypeInfo.ValidateType(type);
JsonTypeInfo typeInfo = CreateJsonTypeInfo(type, options);
typeInfo.OriginatingResolver = this;

// We've finished configuring the metadata, brand the instance as user-unmodified.
// This should be the last update operation in the resolver to avoid resetting the flag.
typeInfo.IsCustomized = false;

if (_modifiers != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ private static void PopulateProperties(JsonTypeInfo typeInfo, Func<JsonSerialize
Debug.Assert(typeInfo.Kind is JsonTypeInfoKind.Object);
Debug.Assert(!typeInfo.IsReadOnly);

JsonSerializerContext? context = typeInfo.Options.TypeInfoResolver as JsonSerializerContext;
JsonSerializerContext? context = (typeInfo.OriginatingResolver ?? typeInfo.Options.TypeInfoResolver) as JsonSerializerContext;
krwq marked this conversation as resolved.
Show resolved Hide resolved
if (propInitFunc?.Invoke(context!) is not JsonPropertyInfo[] properties)
{
if (typeInfo.Type == JsonTypeInfo.ObjectType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ public abstract class JsonPropertyInfo
internal static readonly JsonPropertyInfo s_missingProperty = GetPropertyPlaceholder();

internal JsonTypeInfo? ParentTypeInfo { get; private set; }
private JsonTypeInfo? _jsonTypeInfo;

/// <summary>
/// Converter after applying CustomConverter (i.e. JsonConverterAttribute)
Expand Down Expand Up @@ -266,10 +265,7 @@ internal static JsonPropertyInfo GetPropertyPlaceholder()

private protected void VerifyMutable()
{
if (ParentTypeInfo?.IsReadOnly == true)
{
ThrowHelper.ThrowInvalidOperationException_PropertyInfoImmutable();
}
ParentTypeInfo?.VerifyMutable();
}

internal bool IsConfigured { get; private set; }
Expand Down Expand Up @@ -806,6 +802,15 @@ internal JsonTypeInfo JsonTypeInfo
}
}

private JsonTypeInfo? _jsonTypeInfo;

/// <summary>
/// Returns true if <see cref="JsonTypeInfo"/> has been configured.
/// This might be false even if <see cref="IsConfigured"/> is true
/// in cases of recursive types or <see cref="IsIgnored"/> is true.
/// </summary>
internal bool IsPropertyTypeInfoConfigured => _jsonTypeInfo?.IsConfigured == true;

/// <summary>
/// Property was marked JsonIgnoreCondition.Always and also hasn't been configured by the user.
/// </summary>
Expand Down
Loading