diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextClasses.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextClasses.cs index 24559eb6c86a5..fcb3dc91a886c 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextClasses.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextClasses.cs @@ -46,6 +46,7 @@ public interface ITestContext public JsonTypeInfo StructWithCustomConverterPropertyFactory { get; } public JsonTypeInfo ClassWithBadCustomConverter { get; } public JsonTypeInfo StructWithBadCustomConverter { get; } + public JsonTypeInfo NullablePersonStruct { get; } } internal partial class JsonContext : JsonSerializerContext diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataAndSerializationContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataAndSerializationContextTests.cs index 81513b46499a7..99eecd14cd7ee 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataAndSerializationContextTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataAndSerializationContextTests.cs @@ -40,6 +40,7 @@ namespace System.Text.Json.SourceGeneration.Tests [JsonSerializable(typeof(StructWithCustomConverterPropertyFactory))] [JsonSerializable(typeof(ClassWithBadCustomConverter))] [JsonSerializable(typeof(StructWithBadCustomConverter))] + [JsonSerializable(typeof(PersonStruct?))] internal partial class MetadataAndSerializationContext : JsonSerializerContext, ITestContext { public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Default; @@ -85,6 +86,8 @@ public override void EnsureFastPathGeneratedAsExpected() Assert.NotNull(MetadataAndSerializationContext.Default.StructWithCustomConverterPropertyFactory); Assert.Throws(() => MetadataAndSerializationContext.Default.ClassWithBadCustomConverter); Assert.Throws(() => MetadataAndSerializationContext.Default.StructWithBadCustomConverter); + Assert.Null(MetadataAndSerializationContext.Default.NullablePersonStruct.SerializeHandler); + Assert.NotNull(MetadataAndSerializationContext.Default.PersonStruct.SerializeHandler); } } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataContextTests.cs index f83a2fc923421..6aaaf82d48ef6 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataContextTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataContextTests.cs @@ -39,6 +39,7 @@ namespace System.Text.Json.SourceGeneration.Tests [JsonSerializable(typeof(StructWithCustomConverterPropertyFactory), GenerationMode = JsonSourceGenerationMode.Metadata)] [JsonSerializable(typeof(ClassWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata)] [JsonSerializable(typeof(StructWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(PersonStruct?), GenerationMode = JsonSourceGenerationMode.Metadata)] internal partial class MetadataWithPerTypeAttributeContext : JsonSerializerContext, ITestContext { public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Metadata; @@ -83,6 +84,8 @@ public override void EnsureFastPathGeneratedAsExpected() Assert.Null(MetadataWithPerTypeAttributeContext.Default.StructWithCustomConverterPropertyFactory.SerializeHandler); Assert.Throws(() => MetadataWithPerTypeAttributeContext.Default.ClassWithBadCustomConverter.SerializeHandler); Assert.Throws(() => MetadataWithPerTypeAttributeContext.Default.StructWithBadCustomConverter.SerializeHandler); + Assert.Null(MetadataWithPerTypeAttributeContext.Default.NullablePersonStruct.SerializeHandler); + Assert.Null(MetadataWithPerTypeAttributeContext.Default.PersonStruct.SerializeHandler); } } @@ -120,6 +123,7 @@ public override void EnsureFastPathGeneratedAsExpected() [JsonSerializable(typeof(StructWithCustomConverterPropertyFactory))] [JsonSerializable(typeof(ClassWithBadCustomConverter))] [JsonSerializable(typeof(StructWithBadCustomConverter))] + [JsonSerializable(typeof(PersonStruct?))] internal partial class MetadataContext : JsonSerializerContext, ITestContext { public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Metadata; @@ -187,6 +191,8 @@ public override void EnsureFastPathGeneratedAsExpected() Assert.Null(MetadataContext.Default.StructWithCustomConverterPropertyFactory.SerializeHandler); Assert.Throws(() => MetadataContext.Default.ClassWithBadCustomConverter.SerializeHandler); Assert.Throws(() => MetadataContext.Default.StructWithBadCustomConverter.SerializeHandler); + Assert.Null(MetadataContext.Default.NullablePersonStruct.SerializeHandler); + Assert.Null(MetadataContext.Default.PersonStruct.SerializeHandler); } [Fact] diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MixedModeContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MixedModeContextTests.cs index 060db030bcada..988ab86027b57 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MixedModeContextTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MixedModeContextTests.cs @@ -40,6 +40,7 @@ namespace System.Text.Json.SourceGeneration.Tests [JsonSerializable(typeof(StructWithCustomConverterPropertyFactory), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(ClassWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(StructWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(PersonStruct?), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] internal partial class MixedModeContext : JsonSerializerContext, ITestContext { public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization; @@ -85,6 +86,8 @@ public override void EnsureFastPathGeneratedAsExpected() Assert.Null(MixedModeContext.Default.StructWithCustomConverterPropertyFactory.SerializeHandler); Assert.Throws(() => MixedModeContext.Default.ClassWithBadCustomConverter.SerializeHandler); Assert.Throws(() => MixedModeContext.Default.StructWithBadCustomConverter.SerializeHandler); + Assert.Null(MixedModeContext.Default.NullablePersonStruct.SerializeHandler); + Assert.NotNull(MixedModeContext.Default.PersonStruct.SerializeHandler); } [Fact] diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/RealWorldContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/RealWorldContextTests.cs index ee891562fffee..97b21a4db6b0d 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/RealWorldContextTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/RealWorldContextTests.cs @@ -864,5 +864,22 @@ public void PropertyOrdering() string json = JsonSerializer.Serialize(obj, DefaultContext.MyTypeWithPropertyOrdering); Assert.Equal("{\"C\":0,\"B\":0,\"A\":0}", json); } + + [Fact] + public virtual void NullableStruct() + { + PersonStruct? person = new() + { + FirstName = "Jane", + LastName = "Doe" + }; + + string json = JsonSerializer.Serialize(person, DefaultContext.NullablePersonStruct); + JsonTestHelper.AssertJsonEqual(@"{""FirstName"":""Jane"",""LastName"":""Doe""}", json); + + person = JsonSerializer.Deserialize(json, DefaultContext.NullablePersonStruct); + Assert.Equal("Jane", person.Value.FirstName); + Assert.Equal("Doe", person.Value.LastName); + } } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationContextTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationContextTests.cs index 57f663e8b3633..147cae5e46dc7 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationContextTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationContextTests.cs @@ -40,6 +40,7 @@ namespace System.Text.Json.SourceGeneration.Tests [JsonSerializable(typeof(StructWithCustomConverterPropertyFactory))] [JsonSerializable(typeof(ClassWithBadCustomConverter))] [JsonSerializable(typeof(StructWithBadCustomConverter))] + [JsonSerializable(typeof(PersonStruct?))] internal partial class SerializationContext : JsonSerializerContext, ITestContext { public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Serialization; @@ -78,6 +79,7 @@ internal partial class SerializationContext : JsonSerializerContext, ITestContex [JsonSerializable(typeof(StructWithCustomConverterPropertyFactory), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(ClassWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(StructWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(PersonStruct?), GenerationMode = JsonSourceGenerationMode.Serialization)] internal partial class SerializationWithPerTypeAttributeContext : JsonSerializerContext, ITestContext { public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Serialization; @@ -117,6 +119,7 @@ internal partial class SerializationWithPerTypeAttributeContext : JsonSerializer [JsonSerializable(typeof(StructWithCustomConverterPropertyFactory), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(ClassWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(StructWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(PersonStruct?), GenerationMode = JsonSourceGenerationMode.Serialization)] internal partial class SerializationContextWithCamelCase : JsonSerializerContext, ITestContext { public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Serialization; @@ -164,6 +167,8 @@ public override void EnsureFastPathGeneratedAsExpected() Assert.Null(SerializationContext.Default.StructWithCustomConverterProperty.SerializeHandler); Assert.Throws(() => SerializationContext.Default.ClassWithBadCustomConverter.SerializeHandler); Assert.Throws(() => SerializationContext.Default.StructWithBadCustomConverter.SerializeHandler); + Assert.Null(SerializationContext.Default.NullablePersonStruct.SerializeHandler); + Assert.NotNull(SerializationContext.Default.PersonStruct.SerializeHandler); } [Fact] @@ -431,6 +436,21 @@ public void OnSerializeCallbacks() Assert.Equal("{\"MyProperty\":\"Before\"}", json); Assert.Equal("After", obj.MyProperty); } + + [Fact] + public override void NullableStruct() + { + PersonStruct? person = new() + { + FirstName = "Jane", + LastName = "Doe" + }; + + string json = JsonSerializer.Serialize(person, DefaultContext.NullablePersonStruct); + JsonTestHelper.AssertJsonEqual(@"{""FirstName"":""Jane"",""LastName"":""Doe""}", json); + + Assert.Throws(() => JsonSerializer.Deserialize(json, DefaultContext.NullablePersonStruct)); + } } public sealed class SerializationWithPerTypeAttributeContextTests : SerializationContextTests @@ -470,6 +490,8 @@ public override void EnsureFastPathGeneratedAsExpected() Assert.Null(SerializationWithPerTypeAttributeContext.Default.StructWithCustomConverterPropertyFactory.SerializeHandler); Assert.Throws(() => SerializationWithPerTypeAttributeContext.Default.ClassWithBadCustomConverter.SerializeHandler); Assert.Throws(() => SerializationWithPerTypeAttributeContext.Default.StructWithBadCustomConverter.SerializeHandler); + Assert.Null(SerializationWithPerTypeAttributeContext.Default.NullablePersonStruct.SerializeHandler); + Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.PersonStruct.SerializeHandler); } } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationLogicTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationLogicTests.cs index 2bfb07086eecd..99b1282d1e948 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationLogicTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationLogicTests.cs @@ -5,6 +5,7 @@ using System.IO; using System.Text.Encodings.Web; using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; using Xunit; namespace System.Text.Json.SourceGeneration.Tests @@ -123,5 +124,70 @@ public static void WriterIsFlushedAtRootCall() Assert.Equal(18, writer.BytesCommitted); Assert.Equal(0, writer.BytesPending); } + + [Fact] + public static void FastPathInvokedForNullableUnderlyingType() + { + PersonStruct? person = new() + { + FirstName = "Jane", + LastName = "Doe" + }; + + NullablePersonContext context = new(); + Assert.False(context.FastPathCalled); + string json = JsonSerializer.Serialize(person, context.NullablePersonStruct); + Assert.True(context.FastPathCalled); + JsonTestHelper.AssertJsonEqual(@"{""FirstName"":""Jane"",""LastName"":""Doe""}", json); + } + + internal partial class NullablePersonContext : JsonSerializerContext + { + private static JsonSerializerOptions s_options = new JsonSerializerOptions(); + + public bool FastPathCalled { get; private set; } + + public NullablePersonContext() : base(s_options) + { + } + + protected override JsonSerializerOptions? GeneratedSerializerOptions => s_options; + + public JsonTypeInfo NullablePersonStruct => + JsonMetadataServices.CreateValueInfo(s_options, JsonMetadataServices.GetNullableConverter(underlyingTypeInfo: PersonStruct)); + + public JsonTypeInfo PersonStruct + { + get + { + var objectInfo = new JsonObjectInfoValues() + { + ObjectCreator = static () => new PersonStruct(), + SerializeHandler = PersonStructSerializeHandler + }; + + return JsonMetadataServices.CreateObjectInfo(s_options, objectInfo); + } + } + + private void PersonStructSerializeHandler(Utf8JsonWriter writer, PersonStruct value) + { + FastPathCalled = true; + writer.WriteStartObject(); + writer.WriteString("FirstName", value.FirstName); + writer.WriteString("LastName", value.LastName); + writer.WriteEndObject(); + } + + public override JsonTypeInfo? GetTypeInfo(Type type) + { + if (type == typeof(PersonStruct)) + { + return PersonStruct; + } + + return null; + } + } } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.cs index 887f65b80da83..1acdbdc4d743b 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.cs @@ -151,4 +151,10 @@ public class JsonMessage } internal struct MyStruct { } + + public struct PersonStruct + { + public string FirstName { get; set; } + public string LastName { get; set; } + } }