Skip to content

Commit

Permalink
Support use of fast-path serialization in combined JsonSerializerCont…
Browse files Browse the repository at this point in the history
…exts. (#80741)

* Support use of fast-path serialization in combined JsonSerializerContexts.

* Strengthen assertions in the nested JsonTypeInfo accessors.
  • Loading branch information
eiriktsarpalis authored Feb 8, 2023
1 parent 82d187a commit 0304f1f
Show file tree
Hide file tree
Showing 18 changed files with 465 additions and 63 deletions.
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);
((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;
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

0 comments on commit 0304f1f

Please sign in to comment.