From: Eirik Tsarpalis Date: Fri, 18 Feb 2022 10:44:23 +0000 (+0000) Subject: Support resumable serialization in NullableConverter (#65524) X-Git-Tag: accepted/tizen/unified/riscv/20231226.055536~10740 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=fb69200870cfa03d48d2e2dfee95a51a7d21d838;p=platform%2Fupstream%2Fdotnet%2Fruntime.git Support resumable serialization in NullableConverter (#65524) * Support resumable serialization in NullableConverter * use null instead of default --- diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonCollectionConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonCollectionConverter.cs index 0ca9ddb..6d3a805 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonCollectionConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonCollectionConverter.cs @@ -57,7 +57,7 @@ namespace System.Text.Json.Serialization protected static JsonConverter GetElementConverter(ref WriteStack state) { - JsonConverter converter = (JsonConverter)state.Current.DeclaredJsonPropertyInfo!.ConverterBase; + JsonConverter converter = (JsonConverter)state.Current.JsonPropertyInfo!.ConverterBase; Debug.Assert(converter != null); // It should not be possible to have a null converter at this point. return converter; @@ -282,7 +282,7 @@ namespace System.Text.Json.Serialization writer.WriteStartArray(); } - state.Current.DeclaredJsonPropertyInfo = state.Current.JsonTypeInfo.ElementTypeInfo!.PropertyInfoForTypeInfo; + state.Current.JsonPropertyInfo = state.Current.JsonTypeInfo.ElementTypeInfo!.PropertyInfoForTypeInfo; } success = OnWriteResume(writer, value, options, ref state); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonDictionaryConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonDictionaryConverter.cs index cff2ce4..b086d95 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonDictionaryConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/JsonDictionaryConverter.cs @@ -317,7 +317,7 @@ namespace System.Text.Json.Serialization } } - state.Current.DeclaredJsonPropertyInfo = state.Current.JsonTypeInfo.ElementTypeInfo!.PropertyInfoForTypeInfo; + state.Current.JsonPropertyInfo = state.Current.JsonTypeInfo.ElementTypeInfo!.PropertyInfoForTypeInfo; } bool success = OnWriteResume(writer, dictionary, options, ref state); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/FSharp/FSharpOptionConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/FSharp/FSharpOptionConverter.cs index a02dd44..e8d78a5 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/FSharp/FSharpOptionConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/FSharp/FSharpOptionConverter.cs @@ -66,7 +66,7 @@ namespace System.Text.Json.Serialization.Converters } TElement element = _optionValueGetter(value); - state.Current.DeclaredJsonPropertyInfo = state.Current.JsonTypeInfo.ElementTypeInfo!.PropertyInfoForTypeInfo; + state.Current.JsonPropertyInfo = state.Current.JsonTypeInfo.ElementTypeInfo!.PropertyInfoForTypeInfo; return _elementConverter.TryWrite(writer, element, options, ref state); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/FSharp/FSharpValueOptionConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/FSharp/FSharpValueOptionConverter.cs index 35d1640..b11c346 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/FSharp/FSharpValueOptionConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/FSharp/FSharpValueOptionConverter.cs @@ -67,7 +67,7 @@ namespace System.Text.Json.Serialization.Converters TElement element = _optionValueGetter(ref value); - state.Current.DeclaredJsonPropertyInfo = state.Current.JsonTypeInfo.ElementTypeInfo!.PropertyInfoForTypeInfo; + state.Current.JsonPropertyInfo = state.Current.JsonTypeInfo.ElementTypeInfo!.PropertyInfoForTypeInfo; return _elementConverter.TryWrite(writer, element, options, ref state); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectDefaultConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectDefaultConverter.cs index 3487713..9e543b4 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectDefaultConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectDefaultConverter.cs @@ -279,7 +279,7 @@ namespace System.Text.Json.Serialization.Converters if (jsonPropertyInfo.ShouldSerialize) { // Remember the current property for JsonPath support if an exception is thrown. - state.Current.DeclaredJsonPropertyInfo = jsonPropertyInfo; + state.Current.JsonPropertyInfo = jsonPropertyInfo; state.Current.NumberHandling = jsonPropertyInfo.NumberHandling; bool success = jsonPropertyInfo.GetMemberAndWriteJson(obj, ref state, writer); @@ -295,7 +295,7 @@ namespace System.Text.Json.Serialization.Converters if (dataExtensionProperty?.ShouldSerialize == true) { // Remember the current property for JsonPath support if an exception is thrown. - state.Current.DeclaredJsonPropertyInfo = dataExtensionProperty; + state.Current.JsonPropertyInfo = dataExtensionProperty; state.Current.NumberHandling = dataExtensionProperty.NumberHandling; bool success = dataExtensionProperty.GetMemberAndWriteJsonExtensionData(obj, ref state, writer); @@ -334,7 +334,7 @@ namespace System.Text.Json.Serialization.Converters Debug.Assert(jsonPropertyInfo != null); if (jsonPropertyInfo.ShouldSerialize) { - state.Current.DeclaredJsonPropertyInfo = jsonPropertyInfo; + state.Current.JsonPropertyInfo = jsonPropertyInfo; state.Current.NumberHandling = jsonPropertyInfo.NumberHandling; if (!jsonPropertyInfo.GetMemberAndWriteJson(obj!, ref state, writer)) @@ -366,7 +366,7 @@ namespace System.Text.Json.Serialization.Converters if (dataExtensionProperty?.ShouldSerialize == true) { // Remember the current property for JsonPath support if an exception is thrown. - state.Current.DeclaredJsonPropertyInfo = dataExtensionProperty; + state.Current.JsonPropertyInfo = dataExtensionProperty; state.Current.NumberHandling = dataExtensionProperty.NumberHandling; if (!dataExtensionProperty.GetMemberAndWriteJsonExtensionData(obj, ref state, writer)) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/NullableConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/NullableConverter.cs index ba69c3e..4f671c5 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/NullableConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/NullableConverter.cs @@ -1,73 +1,102 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics; - namespace System.Text.Json.Serialization.Converters { internal sealed class NullableConverter : JsonConverter where T : struct { + internal override ConverterStrategy ConverterStrategy { get; } + internal override Type? ElementType => typeof(T); + public override bool HandleNull => true; + // It is possible to cache the underlying converter since this is an internal converter and // an instance is created only once for each JsonSerializerOptions instance. - private readonly JsonConverter _converter; + private readonly JsonConverter _elementConverter; + + public NullableConverter(JsonConverter elementConverter) + { + _elementConverter = elementConverter; + ConverterStrategy = elementConverter.ConverterStrategy; + IsInternalConverterForNumberType = elementConverter.IsInternalConverterForNumberType; + // temporary workaround for JsonConverter base constructor needing to access + // ConverterStrategy when calculating `CanUseDirectReadOrWrite`. + CanUseDirectReadOrWrite = elementConverter.ConverterStrategy == ConverterStrategy.Value; + } + + internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, ref ReadStack state, out T? value) + { + if (!state.IsContinuation && reader.TokenType == JsonTokenType.Null) + { + value = null; + return true; + } - public NullableConverter(JsonConverter converter) + state.Current.JsonPropertyInfo = state.Current.JsonTypeInfo.ElementTypeInfo!.PropertyInfoForTypeInfo; + if (_elementConverter.TryRead(ref reader, typeof(T), options, ref state, out T element)) + { + value = element; + return true; + } + + value = null; + return false; + } + + internal override bool OnTryWrite(Utf8JsonWriter writer, T? value, JsonSerializerOptions options, ref WriteStack state) { - _converter = converter; - IsInternalConverterForNumberType = converter.IsInternalConverterForNumberType; + if (value is null) + { + writer.WriteNullValue(); + return true; + } + + state.Current.JsonPropertyInfo = state.Current.JsonTypeInfo.ElementTypeInfo!.PropertyInfoForTypeInfo; + return _elementConverter.TryWrite(writer, value.Value, options, ref state); } public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - // We do not check _converter.HandleNull, as the underlying struct cannot be null. - // A custom converter for some type T? can handle null. if (reader.TokenType == JsonTokenType.Null) { return null; } - T value = _converter.Read(ref reader, typeof(T), options); + T value = _elementConverter.Read(ref reader, typeof(T), options); return value; } public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options) { - if (!value.HasValue) + if (value is null) { - // We do not check _converter.HandleNull, as the underlying struct cannot be null. - // A custom converter for some type T? can handle null. writer.WriteNullValue(); } else { - _converter.Write(writer, value.Value, options); + _elementConverter.Write(writer, value.Value, options); } } internal override T? ReadNumberWithCustomHandling(ref Utf8JsonReader reader, JsonNumberHandling numberHandling, JsonSerializerOptions options) { - // We do not check _converter.HandleNull, as the underlying struct cannot be null. - // A custom converter for some type T? can handle null. if (reader.TokenType == JsonTokenType.Null) { return null; } - T value = _converter.ReadNumberWithCustomHandling(ref reader, numberHandling, options); + T value = _elementConverter.ReadNumberWithCustomHandling(ref reader, numberHandling, options); return value; } internal override void WriteNumberWithCustomHandling(Utf8JsonWriter writer, T? value, JsonNumberHandling handling) { - if (!value.HasValue) + if (value is null) { - // We do not check _converter.HandleNull, as the underlying struct cannot be null. - // A custom converter for some type T? can handle null. writer.WriteNullValue(); } else { - _converter.WriteNumberWithCustomHandling(writer, value.Value, handling); + _elementConverter.WriteNumberWithCustomHandling(writer, value.Value, handling); } } } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs index 3deefaf..7b47f43 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs @@ -227,7 +227,7 @@ namespace System.Text.Json.Serialization JsonTypeInfo originalJsonTypeInfo = state.Current.JsonTypeInfo; #endif state.Push(); - Debug.Assert(TypeToConvert.IsAssignableFrom(state.Current.JsonTypeInfo.Type)); + Debug.Assert(TypeToConvert == state.Current.JsonTypeInfo.Type); #if !DEBUG // For performance, only perform validation on internal converters on debug builds. @@ -462,7 +462,7 @@ namespace System.Text.Json.Serialization JsonTypeInfo originalJsonTypeInfo = state.Current.JsonTypeInfo; #endif state.Push(); - Debug.Assert(TypeToConvert.IsAssignableFrom(state.Current.JsonTypeInfo.Type)); + Debug.Assert(TypeToConvert == state.Current.JsonTypeInfo.Type); if (!isContinuation) { @@ -528,7 +528,7 @@ namespace System.Text.Json.Serialization // Extension data properties change how dictionary key naming policies are applied. state.Current.IsWritingExtensionDataProperty = true; - state.Current.DeclaredJsonPropertyInfo = state.Current.JsonTypeInfo.ElementTypeInfo!.PropertyInfoForTypeInfo; + state.Current.JsonPropertyInfo = state.Current.JsonTypeInfo.ElementTypeInfo!.PropertyInfoForTypeInfo; success = dictionaryConverter.OnWriteResume(writer, value, options, ref state); if (success) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs index 927eb05..cc044f3 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServices.cs @@ -80,7 +80,7 @@ namespace System.Text.Json.Serialization.Metadata /// This API is for use by the output of the System.Text.Json source generator and should not be called directly. public static JsonTypeInfo CreateValueInfo(JsonSerializerOptions options, JsonConverter converter) { - JsonTypeInfo info = new JsonTypeInfoInternal(options); + JsonTypeInfo info = new JsonTypeInfoInternal(converter, options); info.PropertyInfoForTypeInfo = CreateJsonPropertyInfoForClassInfo(typeof(T), info, converter, options); converter.ConfigureJsonTypeInfo(info, options); return info; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoInternalOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoInternalOfT.cs index 5aceedb..bbb899e 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoInternalOfT.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoInternalOfT.cs @@ -14,9 +14,10 @@ namespace System.Text.Json.Serialization.Metadata /// /// Creates serialization metadata for a type using a simple converter. /// - public JsonTypeInfoInternal(JsonSerializerOptions options) + public JsonTypeInfoInternal(JsonConverter converter, JsonSerializerOptions options) : base(typeof(T), options) { + ElementType = converter.ElementType; } /// @@ -45,6 +46,7 @@ namespace System.Text.Json.Serialization.Metadata #pragma warning restore CS8714 PropInitFunc = objectInfo.PropertyMetadataInitializer; + ElementType = converter.ElementType; SerializeHandler = objectInfo.SerializeHandler; PropertyInfoForTypeInfo = JsonMetadataServices.CreateJsonPropertyInfoForClassInfo(typeof(T), this, converter, Options); NumberHandling = objectInfo.NumberHandling; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs index a69a280..f966a1a 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs @@ -106,8 +106,8 @@ namespace System.Text.Json internal JsonConverter Initialize(JsonTypeInfo jsonTypeInfo, bool supportContinuation) { Current.JsonTypeInfo = jsonTypeInfo; - Current.DeclaredJsonPropertyInfo = jsonTypeInfo.PropertyInfoForTypeInfo; - Current.NumberHandling = Current.DeclaredJsonPropertyInfo.NumberHandling; + Current.JsonPropertyInfo = jsonTypeInfo.PropertyInfoForTypeInfo; + Current.NumberHandling = Current.JsonPropertyInfo.NumberHandling; JsonSerializerOptions options = jsonTypeInfo.Options; if (options.ReferenceHandlingStrategy != ReferenceHandlingStrategy.None) @@ -141,9 +141,9 @@ namespace System.Text.Json _count++; Current.JsonTypeInfo = jsonTypeInfo; - Current.DeclaredJsonPropertyInfo = jsonTypeInfo.PropertyInfoForTypeInfo; + Current.JsonPropertyInfo = jsonTypeInfo.PropertyInfoForTypeInfo; // Allow number handling on property to win over handling on type. - Current.NumberHandling = numberHandling ?? Current.DeclaredJsonPropertyInfo.NumberHandling; + Current.NumberHandling = numberHandling ?? Current.JsonPropertyInfo.NumberHandling; } } else @@ -347,7 +347,7 @@ namespace System.Text.Json static void AppendStackFrame(StringBuilder sb, ref WriteStackFrame frame) { // Append the property name. - string? propertyName = frame.DeclaredJsonPropertyInfo?.ClrName; + string? propertyName = frame.JsonPropertyInfo?.ClrName; if (propertyName == null) { // Attempt to get the JSON property name from the property name specified in re-entry. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStackFrame.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStackFrame.cs index e42dd99..de718fc 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStackFrame.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStackFrame.cs @@ -34,7 +34,7 @@ namespace System.Text.Json /// For objects, it is either the actual (real) JsonPropertyInfo or the for the class. /// For collections, it is the for the class and current element. /// - public JsonPropertyInfo? DeclaredJsonPropertyInfo; + public JsonPropertyInfo? JsonPropertyInfo; /// /// Used when processing extension data dictionaries. @@ -90,7 +90,7 @@ namespace System.Text.Json public void EndProperty() { - DeclaredJsonPropertyInfo = null!; + JsonPropertyInfo = null!; JsonPropertyNameAsString = null; PolymorphicJsonPropertyInfo = null; PropertyState = StackFramePropertyState.None; @@ -102,7 +102,7 @@ namespace System.Text.Json /// public JsonPropertyInfo GetPolymorphicJsonPropertyInfo() { - return PolymorphicJsonPropertyInfo ?? DeclaredJsonPropertyInfo!; + return PolymorphicJsonPropertyInfo ?? JsonPropertyInfo!; } /// diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs index a5d476b..96e002f 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs @@ -408,7 +408,7 @@ namespace System.Text.Json Debug.Assert(!message.Contains(" Path: ")); // Obtain the type to show in the message. - Type? propertyType = state.Current.DeclaredJsonPropertyInfo?.PropertyType; + Type? propertyType = state.Current.JsonPropertyInfo?.PropertyType; if (propertyType == null) { propertyType = state.Current.JsonTypeInfo.Type; diff --git a/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.AsyncEnumerable.cs b/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.AsyncEnumerable.cs index ac2999d..53a5bbf 100644 --- a/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.AsyncEnumerable.cs +++ b/src/libraries/System.Text.Json/tests/Common/CollectionTests/CollectionTests.AsyncEnumerable.cs @@ -74,6 +74,29 @@ namespace System.Text.Json.Serialization.Tests Assert.Equal(1, asyncEnumerable.TotalDisposedEnumerators); } + [Theory] + [MemberData(nameof(GetAsyncEnumerableSources))] + public async Task WriteNestedAsyncEnumerable_Nullable(IEnumerable source, int delayInterval, int bufferSize) + { + // Primarily tests the ability of NullableConverter to flow async serialization state + + JsonSerializerOptions options = new JsonSerializerOptions + { + DefaultBufferSize = bufferSize, + IncludeFields = true, + }; + + string expectedJson = await JsonSerializerWrapperForString.SerializeWrapper<(IEnumerable, bool)?>((source, false), options); + + using var stream = new Utf8MemoryStream(); + var asyncEnumerable = new MockedAsyncEnumerable(source, delayInterval); + await JsonSerializerWrapperForStream.SerializeWrapper<(IAsyncEnumerable, bool)?>(stream, (asyncEnumerable, false), options); + + JsonTestHelper.AssertJsonEqual(expectedJson, stream.ToString()); + Assert.Equal(1, asyncEnumerable.TotalCreatedEnumerators); + Assert.Equal(1, asyncEnumerable.TotalDisposedEnumerators); + } + [Theory, OuterLoop] [InlineData(5000, 1000, true)] [InlineData(5000, 1000, false)]