未验证 提交 07e87bc4 编写于 作者: E Eirik Tsarpalis 提交者: GitHub

Refactor JsonSerializer metadata reading infrastructure (#67183)

* Refactor metadata reading infrastructure.

* Update src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectConverter.cs
上级 c3843c59
......@@ -476,8 +476,8 @@
<data name="ConstructorMaxOf64Parameters" xml:space="preserve">
<value>The deserialization constructor on type '{0}' may not have more than 64 parameters for deserialization.</value>
</data>
<data name="ObjectWithParameterizedCtorRefMetadataNotHonored" xml:space="preserve">
<value>Reference metadata is not honored when deserializing types using parameterized constructors. See type '{0}'.</value>
<data name="ObjectWithParameterizedCtorRefMetadataNotSupported" xml:space="preserve">
<value>Reference metadata is not supported when deserializing constructor parameters. See type '{0}'.</value>
</data>
<data name="SerializerConverterFactoryReturnsNull" xml:space="preserve">
<value>The converter '{0}' cannot return a null value.</value>
......
......@@ -11,7 +11,7 @@ namespace System.Text.Json.Serialization.Converters
/// </summary>
internal sealed class ArrayConverter<TCollection, TElement> : IEnumerableDefaultConverter<TElement[], TElement>
{
internal override bool CanHaveIdMetadata => false;
internal override bool CanHaveMetadata => false;
protected override void Add(in TElement value, ref ReadStack state)
{
......
......@@ -16,7 +16,7 @@ internal abstract class DictionaryDefaultConverter<TDictionary, TKey, TValue>
where TDictionary : IEnumerable<KeyValuePair<TKey, TValue>>
where TKey : notnull
{
internal override bool CanHaveIdMetadata => true;
internal override bool CanHaveMetadata => true;
protected internal override bool OnWriteResume(
Utf8JsonWriter writer,
......
......@@ -12,7 +12,7 @@ namespace System.Text.Json.Serialization.Converters
internal abstract class IEnumerableDefaultConverter<TCollection, TElement> : JsonCollectionConverter<TCollection, TElement>
where TCollection : IEnumerable<TElement>
{
internal override bool CanHaveIdMetadata => true;
internal override bool CanHaveMetadata => true;
protected override bool OnWriteResume(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options, ref WriteStack state)
{
......
......@@ -17,7 +17,7 @@ protected sealed override void Add(TKey key, in TValue value, JsonSerializerOpti
((Dictionary<TKey, TValue>)state.Current.ReturnValue!)[key] = value;
}
internal sealed override bool CanHaveIdMetadata => false;
internal sealed override bool CanHaveMetadata => false;
protected sealed override void CreateCollection(ref Utf8JsonReader reader, ref ReadStack state)
{
......
......@@ -16,7 +16,7 @@ protected sealed override void Add(in TElement value, ref ReadStack state)
((List<TElement>)state.Current.ReturnValue!).Add(value);
}
internal sealed override bool CanHaveIdMetadata => false;
internal sealed override bool CanHaveMetadata => false;
protected sealed override void CreateCollection(ref Utf8JsonReader reader, ref ReadStack state, JsonSerializerOptions options)
{
......
......@@ -120,16 +120,15 @@ protected static JsonConverter<TElement> GetElementConverter(ref WriteStack stat
}
else
{
// Slower path that supports continuation and preserved references.
// Slower path that supports continuation and reading metadata.
bool preserveReferences = options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve;
if (state.Current.ObjectState == StackFrameObjectState.None)
{
if (reader.TokenType == JsonTokenType.StartArray)
{
state.Current.ObjectState = StackFrameObjectState.PropertyValue;
state.Current.ObjectState = StackFrameObjectState.ReadMetadata;
}
else if (preserveReferences)
else if (state.CanContainMetadata)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
......@@ -147,27 +146,41 @@ protected static JsonConverter<TElement> GetElementConverter(ref WriteStack stat
}
// Handle the metadata properties.
if (preserveReferences && state.Current.ObjectState < StackFrameObjectState.PropertyValue)
if (state.CanContainMetadata && state.Current.ObjectState < StackFrameObjectState.ReadMetadata)
{
if (JsonSerializer.ResolveMetadataForJsonArray<TCollection>(ref reader, ref state, options))
{
if (state.Current.ObjectState == StackFrameObjectState.ReadRefEndObject)
{
// This will never throw since it was previously validated in ResolveMetadataForJsonArray.
value = (TCollection)state.Current.ReturnValue!;
return true;
}
}
else
if (!JsonSerializer.TryReadMetadata(this, ref reader, ref state))
{
value = default;
return false;
}
state.Current.ObjectState = StackFrameObjectState.ReadMetadata;
}
if (state.Current.ObjectState < StackFrameObjectState.CreatedObject)
{
if (state.CanContainMetadata)
{
JsonSerializer.ValidateMetadataForArrayConverter(this, ref reader, ref state);
}
if (state.Current.MetadataPropertyNames == MetadataPropertyName.Ref)
{
value = JsonSerializer.ResolveReferenceId<TCollection>(ref state);
return true;
}
CreateCollection(ref reader, ref state, options);
if (state.Current.MetadataPropertyNames.HasFlag(MetadataPropertyName.Id))
{
Debug.Assert(state.ReferenceId != null);
Debug.Assert(options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve);
Debug.Assert(state.Current.ReturnValue is TCollection);
state.ReferenceResolver.AddReference(state.ReferenceId, state.Current.ReturnValue);
state.ReferenceId = null;
}
state.Current.ObjectState = StackFrameObjectState.CreatedObject;
}
......@@ -222,8 +235,8 @@ protected static JsonConverter<TElement> GetElementConverter(ref WriteStack stat
{
state.Current.ObjectState = StackFrameObjectState.EndToken;
// Read the EndObject token for an array with preserve semantics.
if (state.Current.ValidateEndTokenOnArray)
// Array payload is nested inside a $values metadata property.
if (state.Current.MetadataPropertyNames.HasFlag(MetadataPropertyName.Values))
{
if (!reader.Read())
{
......@@ -235,7 +248,8 @@ protected static JsonConverter<TElement> GetElementConverter(ref WriteStack stat
if (state.Current.ObjectState < StackFrameObjectState.EndTokenValidation)
{
if (state.Current.ValidateEndTokenOnArray)
// Array payload is nested inside a $values metadata property.
if (state.Current.MetadataPropertyNames.HasFlag(MetadataPropertyName.Values))
{
if (reader.TokenType != JsonTokenType.EndObject)
{
......@@ -304,8 +318,5 @@ protected static JsonConverter<TElement> GetElementConverter(ref WriteStack stat
}
protected abstract bool OnWriteResume(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options, ref WriteStack state);
internal sealed override void CreateInstanceForReferenceResolver(ref Utf8JsonReader reader, ref ReadStack state, JsonSerializerOptions options)
=> CreateCollection(ref reader, ref state, options);
}
}
......@@ -155,7 +155,7 @@ protected static JsonConverter<T> GetConverter<T>(JsonTypeInfo typeInfo)
}
else
{
// Slower path that supports continuation and preserved references.
// Slower path that supports continuation and reading metadata.
if (state.Current.ObjectState == StackFrameObjectState.None)
{
......@@ -168,29 +168,42 @@ protected static JsonConverter<T> GetConverter<T>(JsonTypeInfo typeInfo)
}
// Handle the metadata properties.
bool preserveReferences = options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve;
if (preserveReferences && state.Current.ObjectState < StackFrameObjectState.PropertyValue)
if (state.CanContainMetadata && state.Current.ObjectState < StackFrameObjectState.ReadMetadata)
{
if (JsonSerializer.ResolveMetadataForJsonObject<TDictionary>(ref reader, ref state, options))
{
if (state.Current.ObjectState == StackFrameObjectState.ReadRefEndObject)
{
// This will never throw since it was previously validated in ResolveMetadataForJsonObject.
value = (TDictionary)state.Current.ReturnValue!;
return true;
}
}
else
if (!JsonSerializer.TryReadMetadata(this, ref reader, ref state))
{
value = default;
return false;
}
state.Current.ObjectState = StackFrameObjectState.ReadMetadata;
}
// Create the dictionary.
if (state.Current.ObjectState < StackFrameObjectState.CreatedObject)
{
if (state.CanContainMetadata)
{
JsonSerializer.ValidateMetadataForObjectConverter(this, ref reader, ref state);
}
if (state.Current.MetadataPropertyNames == MetadataPropertyName.Ref)
{
value = JsonSerializer.ResolveReferenceId<TDictionary>(ref state);
return true;
}
CreateCollection(ref reader, ref state);
if (state.Current.MetadataPropertyNames.HasFlag(MetadataPropertyName.Id))
{
Debug.Assert(state.ReferenceId != null);
Debug.Assert(options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve);
Debug.Assert(state.Current.ReturnValue is TDictionary);
state.ReferenceResolver.AddReference(state.ReferenceId, state.Current.ReturnValue);
state.ReferenceId = null;
}
state.Current.ObjectState = StackFrameObjectState.CreatedObject;
}
......@@ -225,7 +238,7 @@ protected static JsonConverter<T> GetConverter<T>(JsonTypeInfo typeInfo)
state.Current.PropertyState = StackFramePropertyState.Name;
if (preserveReferences)
if (state.CanContainMetadata)
{
ReadOnlySpan<byte> propertyName = reader.GetSpan();
if (propertyName.Length > 0 && propertyName[0] == '$')
......@@ -334,8 +347,5 @@ static TKey ReadDictionaryKey(JsonConverter<TKey> keyConverter, ref Utf8JsonRead
return success;
}
internal sealed override void CreateInstanceForReferenceResolver(ref Utf8JsonReader reader, ref ReadStack state, JsonSerializerOptions options)
=> CreateCollection(ref reader, ref state);
}
}
......@@ -25,7 +25,7 @@ protected override void Add(TKey key, in TValue value, JsonSerializerOptions opt
((List<Tuple<TKey, TValue>>)state.Current.ReturnValue!).Add (new Tuple<TKey, TValue>(key, value));
}
internal override bool CanHaveIdMetadata => false;
internal override bool CanHaveMetadata => false;
protected override void CreateCollection(ref Utf8JsonReader reader, ref ReadStack state)
{
......
......@@ -41,7 +41,7 @@ internal JsonConverter<T> Converter
internal override bool ConstructorIsParameterized => Converter.ConstructorIsParameterized;
internal override bool CanHaveIdMetadata => Converter.CanHaveIdMetadata;
internal override bool CanHaveMetadata => Converter.CanHaveMetadata;
public JsonMetadataServicesConverter(Func<JsonConverter<T>> converterCreator!!, ConverterStrategy converterStrategy)
{
......@@ -94,8 +94,5 @@ internal override bool OnTryWrite(Utf8JsonWriter writer, T value, JsonSerializer
internal override void ConfigureJsonTypeInfo(JsonTypeInfo jsonTypeInfo, JsonSerializerOptions options)
=> Converter.ConfigureJsonTypeInfo(jsonTypeInfo, options);
internal override void CreateInstanceForReferenceResolver(ref Utf8JsonReader reader, ref ReadStack state, JsonSerializerOptions options)
=> Converter.CreateInstanceForReferenceResolver(ref reader, ref state, options);
}
}
......@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
using System.Text.Json.Nodes;
namespace System.Text.Json.Serialization.Converters
{
......@@ -36,13 +37,15 @@ public override void Write(Utf8JsonWriter writer, object? value, JsonSerializerO
internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, ref ReadStack state, out object? value)
{
object? referenceValue;
if (options.UnknownTypeHandling == JsonUnknownTypeHandling.JsonElement)
{
JsonElement element = JsonElement.ParseValue(ref reader);
// Edge case where we want to lookup for a reference when parsing into typeof(object)
if (options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve &&
JsonSerializer.TryGetReferenceFromJsonElement(ref state, element, out object? referenceValue))
JsonSerializer.TryHandleReferenceFromJsonElement(ref reader, ref state, element, out referenceValue))
{
value = referenceValue;
}
......@@ -55,8 +58,19 @@ internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert,
}
Debug.Assert(options.UnknownTypeHandling == JsonUnknownTypeHandling.JsonNode);
value = JsonNodeConverter.Instance.Read(ref reader, typeToConvert, options)!;
// TODO reference lookup for JsonNode deserialization.
JsonNode node = JsonNodeConverter.Instance.Read(ref reader, typeToConvert, options)!;
if (options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve &&
JsonSerializer.TryHandleReferenceFromJsonNode(ref reader, ref state, node, out referenceValue))
{
value = referenceValue;
}
else
{
value = node;
}
return true;
}
......
......@@ -14,7 +14,7 @@ namespace System.Text.Json.Serialization.Converters
/// </summary>
internal class ObjectDefaultConverter<T> : JsonObjectConverter<T> where T : notnull
{
internal override bool CanHaveIdMetadata => true;
internal override bool CanHaveMetadata => true;
internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, ref ReadStack state, [MaybeNullWhen(false)] out T value)
{
......@@ -72,7 +72,7 @@ internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert,
}
else
{
// Slower path that supports continuation and preserved references.
// Slower path that supports continuation and reading metadata.
if (state.Current.ObjectState == StackFrameObjectState.None)
{
......@@ -85,29 +85,30 @@ internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert,
}
// Handle the metadata properties.
if (state.Current.ObjectState < StackFrameObjectState.PropertyValue)
if (state.CanContainMetadata && state.Current.ObjectState < StackFrameObjectState.ReadMetadata)
{
if (options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve)
if (!JsonSerializer.TryReadMetadata(this, ref reader, ref state))
{
if (JsonSerializer.ResolveMetadataForJsonObject<T>(ref reader, ref state, options))
{
if (state.Current.ObjectState == StackFrameObjectState.ReadRefEndObject)
{
// This will never throw since it was previously validated in ResolveMetadataForJsonObject.
value = (T)state.Current.ReturnValue!;
return true;
}
}
else
{
value = default;
return false;
}
value = default;
return false;
}
state.Current.ObjectState = StackFrameObjectState.ReadMetadata;
}
if (state.Current.ObjectState < StackFrameObjectState.CreatedObject)
{
if (state.CanContainMetadata)
{
JsonSerializer.ValidateMetadataForObjectConverter(this, ref reader, ref state);
}
if (state.Current.MetadataPropertyNames == MetadataPropertyName.Ref)
{
value = JsonSerializer.ResolveReferenceId<T>(ref state);
return true;
}
if (jsonTypeInfo.CreateObject == null)
{
ThrowHelper.ThrowNotSupportedException_DeserializeNoConstructor(jsonTypeInfo.Type, ref reader, ref state);
......@@ -115,6 +116,14 @@ internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert,
obj = jsonTypeInfo.CreateObject!()!;
if (state.Current.MetadataPropertyNames.HasFlag(MetadataPropertyName.Id))
{
Debug.Assert(state.ReferenceId != null);
Debug.Assert(options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve);
state.ReferenceResolver.AddReference(state.ReferenceId, obj);
state.ReferenceId = null;
}
if (obj is IJsonOnDeserializing onDeserializing)
{
onDeserializing.OnDeserializing();
......@@ -453,21 +462,5 @@ protected static bool ReadAheadPropertyValue(ref ReadStack state, ref Utf8JsonRe
return true;
}
internal sealed override void CreateInstanceForReferenceResolver(ref Utf8JsonReader reader, ref ReadStack state, JsonSerializerOptions options)
{
if (state.Current.JsonTypeInfo.CreateObject == null)
{
ThrowHelper.ThrowNotSupportedException_DeserializeNoConstructor(state.Current.JsonTypeInfo.Type, ref reader, ref state);
}
object obj = state.Current.JsonTypeInfo.CreateObject!()!;
state.Current.ReturnValue = obj;
if (obj is IJsonOnDeserializing onDeserializing)
{
onDeserializing.OnDeserializing();
}
}
}
}
......@@ -30,6 +30,11 @@ internal sealed override bool OnTryRead(ref Utf8JsonReader reader, Type typeToCo
{
// Fast path that avoids maintaining state variables.
if (reader.TokenType != JsonTokenType.StartObject)
{
ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(TypeToConvert);
}
ReadOnlySpan<byte> originalSpan = reader.OriginalSpan;
ReadConstructorArguments(ref state, ref reader, options);
......@@ -85,12 +90,46 @@ internal sealed override bool OnTryRead(ref Utf8JsonReader reader, Type typeToCo
}
else
{
// Slower path that supports continuation.
// Slower path that supports continuation and metadata reads.
if (state.Current.ObjectState == StackFrameObjectState.None)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(TypeToConvert);
}
state.Current.ObjectState = StackFrameObjectState.StartToken;
BeginRead(ref state, ref reader, options);
}
// Read any metadata properties.
if (state.CanContainMetadata && state.Current.ObjectState < StackFrameObjectState.ReadMetadata)
{
if (!JsonSerializer.TryReadMetadata(this, ref reader, ref state))
{
value = default;
return false;
}
state.Current.ObjectState = StackFrameObjectState.ReadMetadata;
}
if (state.Current.ObjectState < StackFrameObjectState.ConstructorArguments)
{
if (state.CanContainMetadata)
{
JsonSerializer.ValidateMetadataForObjectConverter(this, ref reader, ref state);
}
if (state.Current.MetadataPropertyNames == MetadataPropertyName.Ref)
{
value = JsonSerializer.ResolveReferenceId<T>(ref state);
return true;
}
BeginRead(ref state, ref reader, options);
state.Current.ObjectState = StackFrameObjectState.ConstructorArguments;
}
if (!ReadConstructorArgumentsWithContinuation(ref state, ref reader, options))
......@@ -101,6 +140,14 @@ internal sealed override bool OnTryRead(ref Utf8JsonReader reader, Type typeToCo
obj = (T)CreateObject(ref state.Current);
if (state.Current.MetadataPropertyNames.HasFlag(MetadataPropertyName.Id))
{
Debug.Assert(state.ReferenceId != null);
Debug.Assert(options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve);
state.ReferenceResolver.AddReference(state.ReferenceId, obj);
state.ReferenceId = null;
}
if (obj is IJsonOnDeserializing onDeserializing)
{
onDeserializing.OnDeserializing();
......@@ -458,11 +505,6 @@ private bool ReadConstructorArgumentsWithContinuation(ref ReadStack state, ref U
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void BeginRead(ref ReadStack state, ref Utf8JsonReader reader, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
ThrowHelper.ThrowJsonException_DeserializeUnableToConvertValue(TypeToConvert);
}
if (state.Current.JsonTypeInfo.ParameterCount != state.Current.JsonTypeInfo.ParameterCache!.Count)
{
ThrowHelper.ThrowInvalidOperationException_ConstructorParameterIncompleteBinding(TypeToConvert);
......
......@@ -31,7 +31,7 @@ public abstract partial class JsonConverter
/// <summary>
/// Can the converter have $id metadata.
/// </summary>
internal virtual bool CanHaveIdMetadata => false;
internal virtual bool CanHaveMetadata => false;
/// <summary>
/// The converter supports polymorphic writes; only reserved for System.Object types.
......@@ -132,10 +132,5 @@ internal static bool ShouldFlush(Utf8JsonWriter writer, ref WriteStack state)
/// </summary>
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
internal virtual void ConfigureJsonTypeInfoUsingReflection(JsonTypeInfo jsonTypeInfo, JsonSerializerOptions options) { }
/// <summary>
/// Creates the instance and assigns it to state.Current.ReturnValue.
/// </summary>
internal virtual void CreateInstanceForReferenceResolver(ref Utf8JsonReader reader, ref ReadStack state, JsonSerializerOptions options) { }
}
}
......@@ -337,8 +337,8 @@ internal bool TryWrite(Utf8JsonWriter writer, in T value, JsonSerializerOptions
break;
case ReferenceHandlingStrategy.Preserve:
bool canHaveIdMetata = polymorphicConverter?.CanHaveIdMetadata ?? CanHaveIdMetadata;
if (canHaveIdMetata && JsonSerializer.TryGetReferenceForValue(value, ref state, writer))
bool canHaveMetadata = polymorphicConverter?.CanHaveMetadata ?? CanHaveMetadata;
if (canHaveMetadata && JsonSerializer.TryGetReferenceForValue(value, ref state, writer))
{
// We found a repeating reference and wrote the relevant metadata; serialization complete.
return true;
......
......@@ -82,7 +82,7 @@ public static partial class JsonSerializer
unescapedPropertyName = propertyName;
}
if (options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve)
if (state.CanContainMetadata)
{
if (propertyName.Length > 0 && propertyName[0] == '$')
{
......
......@@ -20,13 +20,13 @@ public static partial class JsonSerializer
{
if (state.NewReferenceId != null)
{
Debug.Assert(jsonConverter.CanHaveIdMetadata);
Debug.Assert(jsonConverter.CanHaveMetadata);
writer.WriteString(s_metadataId, state.NewReferenceId);
state.NewReferenceId = null;
return MetadataPropertyName.Id;
}
return MetadataPropertyName.NoMetadata;
return MetadataPropertyName.None;
}
internal static MetadataPropertyName WriteReferenceForCollection(
......@@ -36,7 +36,7 @@ public static partial class JsonSerializer
{
if (state.NewReferenceId != null)
{
Debug.Assert(jsonConverter.CanHaveIdMetadata);
Debug.Assert(jsonConverter.CanHaveMetadata);
writer.WriteStartObject();
writer.WriteString(s_metadataId, state.NewReferenceId);
writer.WriteStartArray(s_metadataValues);
......@@ -46,7 +46,7 @@ public static partial class JsonSerializer
// If the jsonConverter supports immutable enumerables or value type collections, don't write any metadata
writer.WriteStartArray();
return MetadataPropertyName.NoMetadata;
return MetadataPropertyName.None;
}
/// <summary>
......
......@@ -3,11 +3,12 @@
namespace System.Text.Json
{
internal enum MetadataPropertyName
[Flags]
internal enum MetadataPropertyName : byte
{
NoMetadata,
Values,
Id,
Ref,
None = 0,
Values = 1,
Id = 2,
Ref = 4,
}
}
......@@ -61,11 +61,21 @@ internal struct ReadStack
/// </summary>
public bool SupportContinuation;
/// <summary>
/// Holds the value of $id or $ref of the currently read object
/// </summary>
public string? ReferenceId;
/// <summary>
/// Whether we can read without the need of saving state for stream and preserve references cases.
/// </summary>
public bool UseFastPath;
/// <summary>
/// Global flag indicating whether the current deserializer supports metadata.
/// </summary>
public bool CanContainMetadata;
/// <summary>
/// Ensures that the stack buffer has sufficient capacity to hold an additional frame.
/// </summary>
......@@ -89,22 +99,18 @@ public void Initialize(Type type, JsonSerializerOptions options, bool supportCon
internal void Initialize(JsonTypeInfo jsonTypeInfo, bool supportContinuation = false)
{
Current.JsonTypeInfo = jsonTypeInfo;
// The initial JsonPropertyInfo will be used to obtain the converter.
Current.JsonPropertyInfo = jsonTypeInfo.PropertyInfoForTypeInfo;
Current.NumberHandling = Current.JsonPropertyInfo.NumberHandling;
JsonSerializerOptions options = jsonTypeInfo.Options;
bool preserveReferences = options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve;
if (preserveReferences)
if (options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve)
{
ReferenceResolver = options.ReferenceHandler!.CreateResolver(writing: false);
CanContainMetadata = true;
}
SupportContinuation = supportContinuation;
UseFastPath = !supportContinuation && !preserveReferences;
Current.JsonTypeInfo = jsonTypeInfo;
Current.JsonPropertyInfo = jsonTypeInfo.PropertyInfoForTypeInfo;
Current.NumberHandling = Current.JsonPropertyInfo.NumberHandling;
UseFastPath = !supportContinuation && !CanContainMetadata;
}
public void Push()
......@@ -140,6 +146,7 @@ public void Push()
// We are re-entering a continuation, adjust indices accordingly
if (_count++ > 0)
{
_stack[_count - 2] = Current;
Current = _stack[_count - 1];
}
......@@ -318,6 +325,24 @@ static void AppendPropertyName(StringBuilder sb, string? propertyName)
}
}
// Traverses the stack for the outermost object being deserialized using constructor parameters
// Only called when calculating exception information.
public JsonTypeInfo GetTopJsonTypeInfoWithParameterizedConstructor()
{
Debug.Assert(!IsContinuation);
for (int i = 0; i < _count - 1; i++)
{
if (_stack[i].JsonTypeInfo.PropertyInfoForTypeInfo.ConverterBase.ConstructorIsParameterized)
{
return _stack[i].JsonTypeInfo;
}
}
Debug.Assert(Current.JsonTypeInfo.PropertyInfoForTypeInfo.ConverterBase.ConstructorIsParameterized);
return Current.JsonTypeInfo;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetConstructorArgumentState()
{
......
......@@ -36,8 +36,8 @@ internal struct ReadStackFrame
public JsonTypeInfo JsonTypeInfo;
public StackFrameObjectState ObjectState; // State tracking the current object.
// Validate EndObject token on array with preserve semantics.
public bool ValidateEndTokenOnArray;
public MetadataPropertyName LatestMetadataPropertyName;
public MetadataPropertyName MetadataPropertyNames;
// For performance, we order the properties by the first deserialize and PropertyIndex helps find the right slot quicker.
public int PropertyIndex;
......@@ -63,7 +63,6 @@ public void EndProperty()
JsonPropertyName = null;
JsonPropertyNameAsString = null;
PropertyState = StackFramePropertyState.None;
ValidateEndTokenOnArray = false;
// No need to clear these since they are overwritten each time:
// NumberHandling
......
......@@ -12,24 +12,8 @@ internal enum StackFrameObjectState : byte
None = 0,
StartToken,
ReadAheadNameOrEndObject, // Try to move the reader to the the first $id, $ref, or the EndObject token.
ReadNameOrEndObject, // Read the first $id, $ref, or the EndObject token.
ReadAheadIdValue, // Try to move the reader to the value for $id.
ReadAheadRefValue, // Try to move the reader to the value for $ref.
ReadIdValue, // Read value for $id.
ReadRefValue, // Read value for $ref.
ReadAheadRefEndObject, // Try to move the reader to the EndObject for $ref.
ReadRefEndObject, // Read the EndObject for $ref.
ReadAheadValuesName, // Try to move the reader to the $values property name.
ReadValuesName, // Read $values property name.
ReadAheadValuesStartArray, // Try to move the reader to the StartArray for $values.
ReadValuesStartArray, // Read the StartArray for $values.
PropertyValue, // Whether all metadata properties has been read.
ReadMetadata,
ConstructorArguments,
CreatedObject,
ReadElements,
EndToken,
......
......@@ -226,15 +226,16 @@ public static void ThrowInvalidOperationException_ConverterCanConvertMultipleTyp
}
[DoesNotReturn]
public static void ThrowNotSupportedException_ObjectWithParameterizedCtorRefMetadataNotHonored(
public static void ThrowNotSupportedException_ObjectWithParameterizedCtorRefMetadataNotSupported(
ReadOnlySpan<byte> propertyName,
ref Utf8JsonReader reader,
ref ReadStack state)
{
JsonTypeInfo jsonTypeInfo = state.GetTopJsonTypeInfoWithParameterizedConstructor();
state.Current.JsonPropertyName = propertyName.ToArray();
NotSupportedException ex = new NotSupportedException(
SR.Format(SR.ObjectWithParameterizedCtorRefMetadataNotHonored, state.Current.JsonTypeInfo.Type));
SR.Format(SR.ObjectWithParameterizedCtorRefMetadataNotSupported, jsonTypeInfo.Type));
ThrowNotSupportedException(ref state, reader, ex);
}
......@@ -558,10 +559,6 @@ public static void ThrowInvalidOperationException_MetadataReferenceOfTypeCannotB
ref Utf8JsonReader reader,
ref ReadStack state)
{
if (state.Current.JsonTypeInfo.PropertyInfoForTypeInfo.ConverterBase.ConstructorIsParameterized)
{
ThrowNotSupportedException_ObjectWithParameterizedCtorRefMetadataNotHonored(propertyName, ref reader, ref state);
}
MetadataPropertyName name = JsonSerializer.GetMetadataPropertyName(propertyName);
if (name == MetadataPropertyName.Id)
......
......@@ -78,7 +78,7 @@ public async Task LeadingReferenceMetadataNotSupported()
string exStr = ex.ToString();
Assert.Contains("System.Text.Json.Serialization.Tests.ConstructorTests+Employee", exStr);
Assert.Contains("$.$id", exStr);
Assert.Contains("$.Manager.$ref", exStr);
}
public class Employee
......@@ -107,10 +107,8 @@ public async Task RandomReferenceMetadataNotSupported()
var options = new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.Preserve };
NotSupportedException ex = await Assert.ThrowsAsync<NotSupportedException>(() => Serializer.DeserializeWrapper<Employee>(json, options));
string exStr = ex.ToString();
Assert.Contains("System.Text.Json.Serialization.Tests.ConstructorTests+Employee", exStr);
Assert.Contains("$.$random", exStr);
JsonException ex = await Assert.ThrowsAsync<JsonException>(() => Serializer.DeserializeWrapper<Employee>(json, options));
Assert.Equal("$.$random", ex.Path);
}
[Fact]
......
......@@ -690,7 +690,7 @@ public async Task ObjectNull()
}";
JsonException ex = await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<Employee>(json, s_deserializerOptionsPreserve));
Assert.Equal("$.$ref", ex.Path);
Assert.Equal("$", ex.Path);
}
[Fact()]
......@@ -702,7 +702,7 @@ public async Task ArrayNull()
}";
JsonException ex = await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<List<Employee>>(json, s_deserializerOptionsPreserve));
Assert.Equal("$.$ref", ex.Path);
Assert.Equal("$", ex.Path);
}
[Fact]
......@@ -714,7 +714,7 @@ public async Task DictionaryNull()
}";
JsonException ex = await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<Dictionary<string, Employee>>(json, s_deserializerOptionsPreserve));
Assert.Equal("$.$ref", ex.Path);
Assert.Equal("$", ex.Path);
}
[Fact]
......@@ -728,7 +728,7 @@ public async Task ObjectPropertyNull()
}";
JsonException ex = await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<Employee>(json, s_deserializerOptionsPreserve));
Assert.Equal("$.Manager.$ref", ex.Path);
Assert.Equal("$.Manager", ex.Path);
}
[Fact]
......@@ -742,7 +742,7 @@ public async Task ArrayPropertyNull()
}";
JsonException ex = await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<Employee>(json, s_deserializerOptionsPreserve));
Assert.Equal("$.Subordinates.$ref", ex.Path);
Assert.Equal("$.Subordinates", ex.Path);
}
[Fact]
......@@ -756,7 +756,7 @@ public async Task DictionaryPropertyNull()
}";
JsonException ex = await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<Employee>(json, s_deserializerOptionsPreserve));
Assert.Equal("$.Contacts.$ref", ex.Path);
Assert.Equal("$.Contacts", ex.Path);
}
#endregion
......@@ -1235,7 +1235,7 @@ public async Task ReferenceObjectBeforePreservedObject()
JsonException ex = await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<List<Employee>>(json, s_deserializerOptionsPreserve));
Assert.Contains("'1'", ex.Message);
Assert.Equal("$[0].$ref", ex.Path);
Assert.Equal("$[0]", ex.Path);
}
[Theory]
......@@ -1315,7 +1315,7 @@ public async Task DuplicatedId()
JsonException ex = await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<List<Employee>>(json, s_deserializerOptionsPreserve));
Assert.Equal("$[1].$id", ex.Path);
Assert.Equal("$[1]", ex.Path);
Assert.Contains("'1'", ex.Message);
}
......@@ -1341,7 +1341,7 @@ public async Task DuplicatedIdArray()
JsonException ex = await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<ClassWithTwoListProperties>(json, s_deserializerOptionsPreserve));
Assert.Equal("$.List2.$id", ex.Path);
Assert.Equal("$.List2.$values", ex.Path);
Assert.Contains("'1'", ex.Message);
}
......@@ -1520,7 +1520,7 @@ public async Task JsonObjectCollectionTest()
}";
JsonException ex = await Assert.ThrowsAsync<JsonException>(async () => await Serializer.DeserializeWrapper<List<string>>(json, s_deserializerOptionsPreserve));
Assert.Equal("$.$ref", ex.Path);
Assert.Equal("$", ex.Path);
// $id Valid under conditions: must be the first property in the object.
// $values Valid under conditions: must be after $id.
......@@ -1572,5 +1572,85 @@ public class BaseAndDerivedWrapper
public class Derived : Base { }
public class Base { }
[Theory]
[InlineData(JsonUnknownTypeHandling.JsonElement)]
[InlineData(JsonUnknownTypeHandling.JsonNode)]
public async Task ObjectConverter_ShouldHandleReferenceMetadata(JsonUnknownTypeHandling typehandling)
{
var options = new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.Preserve, UnknownTypeHandling = typehandling };
string json = @"[{ ""$id"" : ""1"" },{ ""$ref"" : ""1""}]";
object[] deserialized = await Serializer.DeserializeWrapper<object[]>(json, options);
Assert.Same(deserialized[0], deserialized[1]);
}
[Theory]
[InlineData(@"{ ""$id"" : 42 }", JsonUnknownTypeHandling.JsonElement)]
[InlineData(@"{ ""$id"" : 42 }", JsonUnknownTypeHandling.JsonNode)]
[InlineData(@"{ ""$ref"" : 42 }", JsonUnknownTypeHandling.JsonElement)]
[InlineData(@"{ ""$ref"" : 42 }", JsonUnknownTypeHandling.JsonNode)]
public async Task ObjectConverter_InvalidMetadataPropertyType_ShouldThrowJsonException(string json, JsonUnknownTypeHandling typehandling)
{
var options = new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.Preserve, UnknownTypeHandling = typehandling };
await Assert.ThrowsAsync<JsonException>(() => Serializer.DeserializeWrapper<object>(json, options));
}
[Theory]
[InlineData(JsonUnknownTypeHandling.JsonElement)]
[InlineData(JsonUnknownTypeHandling.JsonNode)]
public async Task ObjectConverter_PropertyTrailingRefMetadata_ShouldThrowJsonException(JsonUnknownTypeHandling typehandling)
{
var options = new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.Preserve, UnknownTypeHandling = typehandling };
string json = @"[{ ""$id"" : ""1"" }, { ""$ref"" : ""1"", ""trailingProperty"" : true }]";
await Assert.ThrowsAsync<JsonException>(() => Serializer.DeserializeWrapper<object[]>(json, options));
}
[Fact]
public async Task ConstructorDeserialization_ReferencePreservation()
{
string json = @"[{ ""$id"" : ""1"", ""Value"" : 42, ""Next"" : null }, { ""$ref"" : ""1"" }]";
LinkedList<int>[] deserialized = await Serializer.DeserializeWrapper<LinkedList<int>[]>(json, s_serializerOptionsPreserve);
Assert.Equal(2, deserialized.Length);
Assert.Equal(42, deserialized[0].Value);
Assert.Null(deserialized[0].Next);
Assert.Same(deserialized[0], deserialized[1]);
}
[Theory]
[InlineData(@"{ ""Value"" : 1, ""Next"" : { ""$id"" : ""1"", ""Value"" : 2, ""Next"" : null }}", typeof(LinkedList<int>))]
[InlineData(@"[{ ""$id"" : ""1"", ""Value"" : 2, ""Next"" : null }, { ""Value"" : 1, ""Next"" : { ""$ref"" : ""1""}}]", typeof(LinkedList<int>[]))]
public Task ConstructorDeserialization_NestedConstructorArgumentReference_SupportedScenaria(string json, Type type)
=> Serializer.DeserializeWrapper(json, type, s_serializerOptionsPreserve);
[Theory]
[InlineData(@"{ ""$id"" : ""1"", ""Value"" : 1, ""Next"" : { ""$id"" : ""2"", ""Value"" : 2, ""Next"" : null }}", typeof(LinkedList<int>))]
[InlineData(@"{ ""$id"" : ""1"", ""Value"" : 1, ""Next"" : { ""$ref"" : ""1"" }}", typeof(LinkedList<int>))]
[InlineData(@"[{ ""$id"" : ""1"", ""Value"" : 2, ""Next"" : null }, { ""$id"" : ""2"", ""Value"" : 1, ""Next"" : { ""$ref"" : ""1""}}]", typeof(LinkedList<int>[]))]
[InlineData(@"{ ""$id"" : ""1"", ""Value"" : [{""$id"" : ""2""}], ""Next"" : null }", typeof(LinkedList<Base[]>))]
[InlineData(@"{ ""$id"" : ""1"", ""Value"" : [[{""$id"" : ""2""}]], ""Next"" : null }", typeof(LinkedList<Base[][]>))]
[InlineData(@"{ ""$id"" : ""1"", ""Value"" : [{""$ref"" : ""1""}], ""Next"" : null }", typeof(LinkedList<object[]>))]
[InlineData(@"{ ""$id"" : ""1"", ""PropertyWithSetter"" : { ""$id"" : ""2"" }}", typeof(LinkedList<object?>))]
[InlineData(@"{ ""$id"" : ""1"", ""PropertyWithSetter"" : { ""$ref"" : ""1"" }}", typeof(LinkedList<object?>))]
public async Task ConstructorDeserialization_NestedConstructorArgumentReference_ThrowsNotSupportedException(string json, Type type)
{
NotSupportedException ex = await Assert.ThrowsAsync<NotSupportedException>(() => Serializer.DeserializeWrapper(json, type, s_serializerOptionsPreserve));
Assert.Contains("LinkedList", ex.Message);
}
public class LinkedList<T>
{
[JsonConstructor]
public LinkedList(T value, LinkedList<T>? next)
{
Value = value;
Next = next;
}
public T Value { get; }
public LinkedList<T>? Next { get; }
public T? PropertyWithSetter { get; set; }
}
}
}
......@@ -738,26 +738,6 @@ public async Task PreserveReferenceOfTypeObject()
Assert.Same(rootCopy.Child, rootCopy.Sibling);
}
[Fact]
public async Task PreserveReferenceOfTypeObjectAsync()
{
if (StreamingSerializer is null)
{
return;
}
var root = new ClassWithObjectProperty();
root.Child = new ClassWithObjectProperty();
root.Sibling = root.Child;
Assert.Same(root.Child, root.Sibling);
string json = await StreamingSerializer.SerializeWrapper(root, s_serializerOptionsPreserve);
ClassWithObjectProperty rootCopy = await StreamingSerializer.DeserializeWrapper<ClassWithObjectProperty>(json, s_serializerOptionsPreserve);
Assert.Same(rootCopy.Child, rootCopy.Sibling);
}
[Fact]
public async Task PreserveReferenceOfTypeOfObjectOnCollection()
{
......
......@@ -129,6 +129,12 @@ public ReferenceHandlerTests_Metadata(JsonSerializerWrapper serializer)
[JsonSerializable(typeof(List<object>))]
[JsonSerializable(typeof(StructCollection))]
[JsonSerializable(typeof(ImmutableArray<int>))]
[JsonSerializable(typeof(LinkedList<int>))]
[JsonSerializable(typeof(LinkedList<int>[]))]
[JsonSerializable(typeof(LinkedList<object>))]
[JsonSerializable(typeof(LinkedList<object[]>))]
[JsonSerializable(typeof(LinkedList<Base[]>))]
[JsonSerializable(typeof(LinkedList<Base[][]>))]
internal sealed partial class ReferenceHandlerTestsContext_Metadata : JsonSerializerContext
{
}
......@@ -261,6 +267,12 @@ public ReferenceHandlerTests_Default(JsonSerializerWrapper serializer)
[JsonSerializable(typeof(List<object>))]
[JsonSerializable(typeof(StructCollection))]
[JsonSerializable(typeof(ImmutableArray<int>))]
[JsonSerializable(typeof(LinkedList<int>))]
[JsonSerializable(typeof(LinkedList<int>[]))]
[JsonSerializable(typeof(LinkedList<object>))]
[JsonSerializable(typeof(LinkedList<object[]>))]
[JsonSerializable(typeof(LinkedList<Base[]>))]
[JsonSerializable(typeof(LinkedList<Base[][]>))]
internal sealed partial class ReferenceHandlerTestsContext_Default : JsonSerializerContext
{
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册