From bf7fb2ecbf69deb6a73bb8bbca1e56e7beec8d8a Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Tue, 30 May 2023 19:25:02 +0100 Subject: [PATCH] Fix deserialization of nullable structs with constructors. (#86896) --- .../Converters/Value/NullableConverter.cs | 2 ++ .../ConstructorTests.AttributePresence.cs | 11 +++++++++++ .../Common/PropertyVisibilityTests.InitOnly.cs | 14 ++++++++++++++ .../Serialization/ConstructorTests.cs | 2 ++ .../Serialization/PropertyVisibilityTests.cs | 2 ++ 5 files changed, 31 insertions(+) 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 fcc9926f7930a..7bf8b73359a5e 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 @@ -10,6 +10,7 @@ internal sealed class NullableConverter : JsonConverter where T : struct internal override Type? ElementType => typeof(T); public override bool HandleNull => true; internal override bool CanPopulate => _elementConverter.CanPopulate; + internal override bool ConstructorIsParameterized => _elementConverter.ConstructorIsParameterized; // 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. @@ -20,6 +21,7 @@ public NullableConverter(JsonConverter elementConverter) _elementConverter = elementConverter; IsInternalConverterForNumberType = elementConverter.IsInternalConverterForNumberType; ConverterStrategy = elementConverter.ConverterStrategy; + ConstructorInfo = elementConverter.ConstructorInfo; } internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, scoped ref ReadStack state, out T? value) diff --git a/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.AttributePresence.cs b/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.AttributePresence.cs index 11fbed0776d1c..4567db9c4f18a 100644 --- a/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.AttributePresence.cs +++ b/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.AttributePresence.cs @@ -160,5 +160,16 @@ public async Task Struct_Use_DefaultCtor_ByDefault() Assert.Equal(1, point2.X); Assert.Equal(2, point2.Y); } + + [Fact] + public async Task CanDeserializeNullableStructWithCtor() + { + string json = @"{""X"":1,""Y"":2}"; + + Point_2D_Struct_WithAttribute? point2 = await Serializer.DeserializeWrapper(json); + Assert.NotNull(point2); + Assert.Equal(1, point2.Value.X); + Assert.Equal(2, point2.Value.Y); + } } } diff --git a/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.InitOnly.cs b/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.InitOnly.cs index c9be69de1fc5a..a0547a4bf55ff 100644 --- a/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.InitOnly.cs +++ b/src/libraries/System.Text.Json/tests/Common/PropertyVisibilityTests.InitOnly.cs @@ -64,6 +64,20 @@ public virtual async Task NonPublicInitOnlySetter_With_JsonInclude(Type type) Assert.Equal(@"{""MyInt"":1}", await Serializer.SerializeWrapper(obj)); } + [Fact] + public async Task NullableStructWithInitOnlyProperty() + { + // Regression test for https://github.com/dotnet/runtime/issues/86483 + + StructWithInitOnlyProperty? value = new StructWithInitOnlyProperty { MyInt = 42 }; + string json = await Serializer.SerializeWrapper(value); + + Assert.Equal("""{"MyInt":42}""", json); + + StructWithInitOnlyProperty? deserializedValue = await Serializer.DeserializeWrapper(json); + Assert.Equal(deserializedValue, value); + } + public class ClassWithInitOnlyProperty { public int MyInt { get; init; } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ConstructorTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ConstructorTests.cs index 99c2b0ecf4bcb..2b4a718dec4df 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ConstructorTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/ConstructorTests.cs @@ -58,6 +58,7 @@ protected ConstructorTests_Metadata(JsonSerializerWrapper stringWrapper) [JsonSerializable(typeof(Parameterized_WrapperForICollection))] [JsonSerializable(typeof(Point_2D_Struct))] [JsonSerializable(typeof(Point_2D_Struct_WithAttribute))] + [JsonSerializable(typeof(Point_2D_Struct_WithAttribute?))] [JsonSerializable(typeof(ObjWCtorMixedParams))] [JsonSerializable(typeof(Person_Class))] [JsonSerializable(typeof(Point_2D))] @@ -200,6 +201,7 @@ public ConstructorTests_Default(JsonSerializerWrapper jsonSerializer) : base(jso [JsonSerializable(typeof(Parameterized_WrapperForICollection))] [JsonSerializable(typeof(Point_2D_Struct))] [JsonSerializable(typeof(Point_2D_Struct_WithAttribute))] + [JsonSerializable(typeof(Point_2D_Struct_WithAttribute?))] [JsonSerializable(typeof(ObjWCtorMixedParams))] [JsonSerializable(typeof(Person_Class))] [JsonSerializable(typeof(Point_2D))] diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyVisibilityTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyVisibilityTests.cs index d8844690fd06c..3c15522874cfb 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyVisibilityTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyVisibilityTests.cs @@ -162,6 +162,7 @@ public override async Task HonorJsonPropertyName_PrivateSetter() [JsonSerializable(typeof(ClassWithMissingObjectProperty))] [JsonSerializable(typeof(ClassWithInitOnlyProperty))] [JsonSerializable(typeof(StructWithInitOnlyProperty))] + [JsonSerializable(typeof(StructWithInitOnlyProperty?))] [JsonSerializable(typeof(ClassWithCustomNamedInitOnlyProperty))] [JsonSerializable(typeof(StructWithCustomNamedInitOnlyProperty))] [JsonSerializable(typeof(MyClassWithValueTypeInterfaceProperty))] @@ -406,6 +407,7 @@ public void PublicContextAndJsonSerializerOptions() [JsonSerializable(typeof(ClassWithMissingObjectProperty))] [JsonSerializable(typeof(ClassWithInitOnlyProperty))] [JsonSerializable(typeof(StructWithInitOnlyProperty))] + [JsonSerializable(typeof(StructWithInitOnlyProperty?))] [JsonSerializable(typeof(ClassWithCustomNamedInitOnlyProperty))] [JsonSerializable(typeof(StructWithCustomNamedInitOnlyProperty))] [JsonSerializable(typeof(MyClassWithValueTypeInterfaceProperty))]