From 386328587b078a29492b7942fed8cb8f4be237d6 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Mon, 30 Aug 2021 16:19:01 -0500 Subject: [PATCH 1/4] Support JsonConverterFactory with src-gen --- .../gen/JsonSourceGenerator.Emitter.cs | 68 +++-- .../ContextClasses.cs | 3 + .../MetadataAndSerializationContextTests.cs | 6 + .../MetadataContextTests.cs | 12 + .../MixedModeContextTests.cs | 6 + .../RealWorldContextTests.cs | 19 +- .../SerializationContextTests.cs | 15 + ...em.Text.Json.SourceGeneration.Tests.csproj | 3 +- .../TestClasses.CustomConverters.cs | 266 ++++++++++++++++++ .../TestClasses.cs | 129 --------- 10 files changed, 360 insertions(+), 167 deletions(-) create mode 100644 src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.CustomConverters.cs diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index 77f8e7861cfb2..8a49552328c83 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -283,54 +283,50 @@ private string GenerateForTypeWithUnknownConverter(TypeGenerationSpec typeMetada string typeCompilableName = typeMetadata.TypeRef; string typeFriendlyName = typeMetadata.TypeInfoPropertyName; - string metadataInitSource; - // TODO (https://github.com/dotnet/runtime/issues/52218): consider moving this verification source to common helper. + StringBuilder metadataInitSource = new( + $@"{JsonConverterTypeRef} converter = {typeMetadata.ConverterInstantiationLogic}; + {TypeTypeRef} typeToConvert = typeof({typeCompilableName});"); + if (typeMetadata.IsValueType) { - metadataInitSource = $@"{JsonConverterTypeRef} converter = {typeMetadata.ConverterInstantiationLogic}; - {TypeTypeRef} typeToConvert = typeof({typeCompilableName}); - if (!converter.CanConvert(typeToConvert)) - {{ - {TypeTypeRef}? underlyingType = {NullableTypeRef}.GetUnderlyingType(typeToConvert); - if (underlyingType != null && converter.CanConvert(underlyingType)) + metadataInitSource.Append($@" + if (!converter.CanConvert(typeToConvert)) {{ - {JsonConverterTypeRef}? actualConverter = converter; - - if (converter is {JsonConverterFactoryTypeRef} converterFactory) + {TypeTypeRef}? underlyingType = {NullableTypeRef}.GetUnderlyingType(typeToConvert); + if (underlyingType != null && converter.CanConvert(underlyingType)) {{ - actualConverter = converterFactory.CreateConverter(underlyingType, {OptionsInstanceVariableName}); - - if (actualConverter == null || actualConverter is {JsonConverterFactoryTypeRef}) - {{ - throw new {InvalidOperationExceptionTypeRef}($""JsonConverterFactory '{{converter}} cannot return a 'null' or 'JsonConverterFactory' value.""); - }} + // Allow nullable handling to forward to the underlying type's converter. + converter = {JsonMetadataServicesTypeRef}.GetNullableConverter<{typeCompilableName}>(this.{typeFriendlyName})!; }} - - // Allow nullable handling to forward to the underlying type's converter. - converter = {JsonMetadataServicesTypeRef}.GetNullableConverter<{typeCompilableName}>(this.{typeFriendlyName}); - }} - else - {{ - throw new {InvalidOperationExceptionTypeRef}($""The converter '{{converter.GetType()}}' is not compatible with the type '{{typeToConvert}}'.""); - }} - }} - - _{typeFriendlyName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, converter);"; + else + {{ + throw new {InvalidOperationExceptionTypeRef}($""The converter '{{converter.GetType()}}' is not compatible with the type '{{typeToConvert}}'.""); + }} + }}"); } else { - metadataInitSource = $@"{JsonConverterTypeRef} converter = {typeMetadata.ConverterInstantiationLogic}; - {TypeTypeRef} typeToConvert = typeof({typeCompilableName}); - if (!converter.CanConvert(typeToConvert)) + metadataInitSource.Append($@" + if (!converter.CanConvert(typeToConvert)) + {{ + throw new {InvalidOperationExceptionTypeRef}($""The converter '{{converter.GetType()}}' is not compatible with the type '{{typeToConvert}}'.""); + }}"); + } + + metadataInitSource.Append($@" + else if (converter is { JsonConverterFactoryTypeRef } factory) {{ - throw new {InvalidOperationExceptionTypeRef}($""The converter '{{converter.GetType()}}' is not compatible with the type '{{typeToConvert}}'.""); + {JsonConverterTypeRef}? actualConverter = factory.CreateConverter(typeToConvert, { OptionsInstanceVariableName }); + if (actualConverter == null || actualConverter is { JsonConverterFactoryTypeRef }) + {{ + throw new { InvalidOperationExceptionTypeRef }($""JsonConverterFactory '{{factory.GetType()}}' cannot return a 'null' or 'JsonConverterFactory' value.""); + }} + converter = actualConverter; }} + _{typeFriendlyName} = { JsonMetadataServicesTypeRef }.{ GetCreateValueInfoMethodRef(typeCompilableName)} ({ OptionsInstanceVariableName}, converter); "); - _{typeFriendlyName} = {JsonMetadataServicesTypeRef}.{GetCreateValueInfoMethodRef(typeCompilableName)}({OptionsInstanceVariableName}, converter);"; - } - - return GenerateForType(typeMetadata, metadataInitSource); + return GenerateForType(typeMetadata, metadataInitSource.ToString()); } private string GenerateForNullable(TypeGenerationSpec typeMetadata) 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 ab9352249395a..04b2a39f19fb8 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 @@ -32,9 +32,12 @@ public interface ITestContext public JsonTypeInfo String { get; } public JsonTypeInfo ClassWithEnumAndNullable { get; } public JsonTypeInfo ClassWithCustomConverter { get; } + public JsonTypeInfo ClassWithCustomConverterFactory { get; } public JsonTypeInfo StructWithCustomConverter { get; } public JsonTypeInfo ClassWithCustomConverterProperty { get; } public JsonTypeInfo StructWithCustomConverterProperty { get; } + public JsonTypeInfo StructWithCustomConverterFactory { get; } + public JsonTypeInfo EnumWithJsonStringEnumConverter { get; } public JsonTypeInfo ClassWithBadCustomConverter { get; } public JsonTypeInfo StructWithBadCustomConverter { get; } } 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 6db87ac740cae..52a9d6936e679 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 @@ -28,9 +28,12 @@ namespace System.Text.Json.SourceGeneration.Tests [JsonSerializable(typeof(string))] [JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable))] [JsonSerializable(typeof(ClassWithCustomConverter))] + [JsonSerializable(typeof(ClassWithCustomConverterFactory))] [JsonSerializable(typeof(StructWithCustomConverter))] [JsonSerializable(typeof(ClassWithCustomConverterProperty))] [JsonSerializable(typeof(StructWithCustomConverterProperty))] + [JsonSerializable(typeof(StructWithCustomConverterFactory))] + [JsonSerializable(typeof(EnumWithJsonStringEnumConverter))] [JsonSerializable(typeof(ClassWithBadCustomConverter))] [JsonSerializable(typeof(StructWithBadCustomConverter))] internal partial class MetadataAndSerializationContext : JsonSerializerContext, ITestContext @@ -66,9 +69,12 @@ public override void EnsureFastPathGeneratedAsExpected() Assert.Null(MetadataAndSerializationContext.Default.String.Serialize); Assert.NotNull(MetadataAndSerializationContext.Default.ClassWithEnumAndNullable.Serialize); Assert.NotNull(MetadataAndSerializationContext.Default.ClassWithCustomConverter); + Assert.NotNull(MetadataAndSerializationContext.Default.ClassWithCustomConverterFactory); Assert.NotNull(MetadataAndSerializationContext.Default.StructWithCustomConverter); Assert.NotNull(MetadataAndSerializationContext.Default.ClassWithCustomConverterProperty); Assert.NotNull(MetadataAndSerializationContext.Default.StructWithCustomConverterProperty); + Assert.NotNull(MetadataAndSerializationContext.Default.StructWithCustomConverterFactory); + Assert.NotNull(MetadataAndSerializationContext.Default.EnumWithJsonStringEnumConverter); Assert.Throws(() => MetadataAndSerializationContext.Default.ClassWithBadCustomConverter); Assert.Throws(() => MetadataAndSerializationContext.Default.StructWithBadCustomConverter); } 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 50f52d6f9aa6e..618f6907ca149 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 @@ -27,11 +27,14 @@ namespace System.Text.Json.SourceGeneration.Tests [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.Metadata)] [JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Metadata)] [JsonSerializable(typeof(ClassWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(ClassWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Metadata)] [JsonSerializable(typeof(StructWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata)] [JsonSerializable(typeof(ClassWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata)] [JsonSerializable(typeof(StructWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata)] [JsonSerializable(typeof(ClassWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Metadata)] [JsonSerializable(typeof(StructWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(StructWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(EnumWithJsonStringEnumConverter), GenerationMode = JsonSourceGenerationMode.Metadata)] [JsonSerializable(typeof(ClassWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata)] [JsonSerializable(typeof(StructWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata)] internal partial class MetadataWithPerTypeAttributeContext : JsonSerializerContext, ITestContext @@ -65,7 +68,10 @@ public override void EnsureFastPathGeneratedAsExpected() Assert.Null(MetadataWithPerTypeAttributeContext.Default.String.Serialize); Assert.Null(MetadataWithPerTypeAttributeContext.Default.ClassWithEnumAndNullable.Serialize); Assert.Null(MetadataWithPerTypeAttributeContext.Default.ClassWithCustomConverter.Serialize); + Assert.Null(MetadataWithPerTypeAttributeContext.Default.ClassWithCustomConverterFactory.Serialize); Assert.Null(MetadataWithPerTypeAttributeContext.Default.StructWithCustomConverter.Serialize); + Assert.Null(MetadataWithPerTypeAttributeContext.Default.StructWithCustomConverterFactory.Serialize); + Assert.Null(MetadataWithPerTypeAttributeContext.Default.EnumWithJsonStringEnumConverter.Serialize); Assert.Throws(() => MetadataWithPerTypeAttributeContext.Default.ClassWithBadCustomConverter.Serialize); Assert.Throws(() => MetadataWithPerTypeAttributeContext.Default.StructWithBadCustomConverter.Serialize); } @@ -93,9 +99,12 @@ public override void EnsureFastPathGeneratedAsExpected() [JsonSerializable(typeof(string))] [JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable))] [JsonSerializable(typeof(ClassWithCustomConverter))] + [JsonSerializable(typeof(ClassWithCustomConverterFactory))] [JsonSerializable(typeof(StructWithCustomConverter))] [JsonSerializable(typeof(ClassWithCustomConverterProperty))] [JsonSerializable(typeof(StructWithCustomConverterProperty))] + [JsonSerializable(typeof(StructWithCustomConverterFactory))] + [JsonSerializable(typeof(EnumWithJsonStringEnumConverter))] [JsonSerializable(typeof(ClassWithBadCustomConverter))] [JsonSerializable(typeof(StructWithBadCustomConverter))] internal partial class MetadataContext : JsonSerializerContext, ITestContext @@ -131,9 +140,12 @@ public override void EnsureFastPathGeneratedAsExpected() Assert.Null(MetadataContext.Default.String.Serialize); Assert.Null(MetadataContext.Default.ClassWithEnumAndNullable.Serialize); Assert.Null(MetadataContext.Default.ClassWithCustomConverter.Serialize); + Assert.Null(MetadataContext.Default.ClassWithCustomConverterFactory.Serialize); Assert.Null(MetadataContext.Default.StructWithCustomConverter.Serialize); Assert.Null(MetadataContext.Default.ClassWithCustomConverterProperty.Serialize); Assert.Null(MetadataContext.Default.StructWithCustomConverterProperty.Serialize); + Assert.Null(MetadataContext.Default.StructWithCustomConverterFactory.Serialize); + Assert.Null(MetadataContext.Default.EnumWithJsonStringEnumConverter.Serialize); Assert.Throws(() => MetadataContext.Default.ClassWithBadCustomConverter.Serialize); Assert.Throws(() => MetadataContext.Default.StructWithBadCustomConverter.Serialize); } 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 d45996341f800..0e0898cf79e7c 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 @@ -27,9 +27,12 @@ namespace System.Text.Json.SourceGeneration.Tests [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(ClassWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(ClassWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(StructWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(ClassWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(StructWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(StructWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(EnumWithJsonStringEnumConverter), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(ClassWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(StructWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] internal partial class MixedModeContext : JsonSerializerContext, ITestContext @@ -64,9 +67,12 @@ public override void EnsureFastPathGeneratedAsExpected() Assert.Null(MixedModeContext.Default.String.Serialize); Assert.NotNull(MixedModeContext.Default.ClassWithEnumAndNullable.Serialize); Assert.Null(MixedModeContext.Default.ClassWithCustomConverter.Serialize); + Assert.Null(MixedModeContext.Default.ClassWithCustomConverterFactory.Serialize); Assert.Null(MixedModeContext.Default.StructWithCustomConverter.Serialize); Assert.Null(MixedModeContext.Default.ClassWithCustomConverterProperty.Serialize); Assert.Null(MixedModeContext.Default.StructWithCustomConverterProperty.Serialize); + Assert.Null(MixedModeContext.Default.StructWithCustomConverterFactory.Serialize); + Assert.Null(MixedModeContext.Default.EnumWithJsonStringEnumConverter.Serialize); Assert.Throws(() => MixedModeContext.Default.ClassWithBadCustomConverter.Serialize); Assert.Throws(() => MixedModeContext.Default.StructWithBadCustomConverter.Serialize); } 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 5fc9bf7fb73f1..19d0143ca4a62 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 @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; @@ -127,6 +127,23 @@ public virtual void RoundTripWithCustomConverter_Class() Assert.Equal(42, obj.MyInt); } + [Fact] + public virtual void RoundTripWithCustomConverterFactory_Class() + { + const string Json = "{\"MyInt\":142}"; + + ClassWithCustomConverterFactory obj = new() + { + MyInt = 42 + }; + + string json = JsonSerializer.Serialize(obj, DefaultContext.ClassWithCustomConverterFactory); + Assert.Equal(Json, json); + + obj = JsonSerializer.Deserialize(Json, DefaultContext.ClassWithCustomConverterFactory); + Assert.Equal(42, obj.MyInt); + } + [Fact] public virtual void RoundTripWithCustomConverter_Struct() { 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 ae87218f2f782..02e1370a62119 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 @@ -28,9 +28,12 @@ namespace System.Text.Json.SourceGeneration.Tests [JsonSerializable(typeof(string))] [JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable))] [JsonSerializable(typeof(ClassWithCustomConverter))] + [JsonSerializable(typeof(ClassWithCustomConverterFactory))] [JsonSerializable(typeof(StructWithCustomConverter))] [JsonSerializable(typeof(ClassWithCustomConverterProperty))] [JsonSerializable(typeof(StructWithCustomConverterProperty))] + [JsonSerializable(typeof(StructWithCustomConverterFactory))] + [JsonSerializable(typeof(EnumWithJsonStringEnumConverter))] [JsonSerializable(typeof(ClassWithBadCustomConverter))] [JsonSerializable(typeof(StructWithBadCustomConverter))] internal partial class SerializationContext : JsonSerializerContext, ITestContext @@ -59,9 +62,12 @@ internal partial class SerializationContext : JsonSerializerContext, ITestContex [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(ClassWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(ClassWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(StructWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(ClassWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(StructWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(StructWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(EnumWithJsonStringEnumConverter), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(ClassWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(StructWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)] internal partial class SerializationWithPerTypeAttributeContext : JsonSerializerContext, ITestContext @@ -91,9 +97,12 @@ internal partial class SerializationWithPerTypeAttributeContext : JsonSerializer [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(ClassWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(ClassWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(StructWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(ClassWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(StructWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(StructWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(EnumWithJsonStringEnumConverter), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(ClassWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(StructWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)] internal partial class SerializationContextWithCamelCase : JsonSerializerContext, ITestContext @@ -134,9 +143,12 @@ public override void EnsureFastPathGeneratedAsExpected() Assert.Null(SerializationContext.Default.String.Serialize); Assert.NotNull(SerializationContext.Default.ClassWithEnumAndNullable.Serialize); Assert.Null(SerializationContext.Default.ClassWithCustomConverter.Serialize); + Assert.Null(SerializationContext.Default.ClassWithCustomConverterFactory.Serialize); Assert.Null(SerializationContext.Default.StructWithCustomConverter.Serialize); Assert.Null(SerializationContext.Default.ClassWithCustomConverterProperty.Serialize); Assert.Null(SerializationContext.Default.StructWithCustomConverterProperty.Serialize); + Assert.Null(SerializationContext.Default.StructWithCustomConverterFactory.Serialize); + Assert.Null(SerializationContext.Default.EnumWithJsonStringEnumConverter.Serialize); Assert.Throws(() => SerializationContext.Default.ClassWithBadCustomConverter.Serialize); Assert.Throws(() => SerializationContext.Default.StructWithBadCustomConverter.Serialize); } @@ -398,9 +410,12 @@ public override void EnsureFastPathGeneratedAsExpected() Assert.Null(SerializationWithPerTypeAttributeContext.Default.String.Serialize); Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.ClassWithEnumAndNullable.Serialize); Assert.Null(SerializationWithPerTypeAttributeContext.Default.ClassWithCustomConverter.Serialize); + Assert.Null(SerializationWithPerTypeAttributeContext.Default.ClassWithCustomConverterFactory.Serialize); Assert.Null(SerializationWithPerTypeAttributeContext.Default.StructWithCustomConverter.Serialize); Assert.Null(SerializationWithPerTypeAttributeContext.Default.ClassWithCustomConverterProperty.Serialize); Assert.Null(SerializationWithPerTypeAttributeContext.Default.StructWithCustomConverterProperty.Serialize); + Assert.Null(SerializationWithPerTypeAttributeContext.Default.StructWithCustomConverterFactory.Serialize); + Assert.Null(SerializationWithPerTypeAttributeContext.Default.EnumWithJsonStringEnumConverter.Serialize); Assert.Throws(() => SerializationWithPerTypeAttributeContext.Default.ClassWithBadCustomConverter.Serialize); Assert.Throws(() => SerializationWithPerTypeAttributeContext.Default.StructWithBadCustomConverter.Serialize); } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.csproj index e848e3e568992..610accb41c322 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.csproj +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Tests.csproj @@ -71,12 +71,13 @@ + - + diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.CustomConverters.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.CustomConverters.cs new file mode 100644 index 0000000000000..490dafee85714 --- /dev/null +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.CustomConverters.cs @@ -0,0 +1,266 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json.Serialization; + +namespace System.Text.Json.SourceGeneration.Tests +{ + /// + /// Custom converter that adds\substract 100 from MyIntProperty. + /// + public class CustomConverter_ClassWithCustomConverter : JsonConverter + { + public override ClassWithCustomConverter Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException("No StartObject"); + } + + ClassWithCustomConverter obj = new(); + + reader.Read(); + if (reader.TokenType != JsonTokenType.PropertyName && + reader.GetString() != "MyInt") + { + throw new JsonException("Wrong property name"); + } + + reader.Read(); + obj.MyInt = reader.GetInt32() - 100; + + reader.Read(); + if (reader.TokenType != JsonTokenType.EndObject) + { + throw new JsonException("No EndObject"); + } + + return obj; + } + + public override void Write(Utf8JsonWriter writer, ClassWithCustomConverter value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + writer.WriteNumber(nameof(ClassWithCustomConverter.MyInt), value.MyInt + 100); + writer.WriteEndObject(); + } + } + + /// + /// Custom converter that adds\substract 100 from MyIntProperty. + /// + public class CustomConverter_ClassWithCustomConverterFactory : JsonConverter + { + public override ClassWithCustomConverterFactory Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException("No StartObject"); + } + + ClassWithCustomConverterFactory obj = new(); + + reader.Read(); + if (reader.TokenType != JsonTokenType.PropertyName && + reader.GetString() != "MyInt") + { + throw new JsonException("Wrong property name"); + } + + reader.Read(); + obj.MyInt = reader.GetInt32() - 100; + + reader.Read(); + if (reader.TokenType != JsonTokenType.EndObject) + { + throw new JsonException("No EndObject"); + } + + return obj; + } + + public override void Write(Utf8JsonWriter writer, ClassWithCustomConverterFactory value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + writer.WriteNumber(nameof(ClassWithCustomConverterFactory.MyInt), value.MyInt + 100); + writer.WriteEndObject(); + } + } + + [JsonConverter(typeof(CustomConverter_ClassWithCustomConverter))] + public class ClassWithCustomConverter + { + public int MyInt { get; set; } + } + + [JsonConverter(typeof(CustomConverterFactory))] + public class ClassWithCustomConverterFactory + { + public int MyInt { get; set; } + } + + [JsonConverter(typeof(CustomConverter_StructWithCustomConverter))] // Invalid + public class ClassWithBadCustomConverter + { + public int MyInt { get; set; } + } + + /// + /// Custom converter that adds\substract 100 from MyIntProperty. + /// + public class CustomConverter_StructWithCustomConverter : JsonConverter + { + public override StructWithCustomConverter Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException("No StartObject"); + } + + StructWithCustomConverter obj = new(); + + reader.Read(); + if (reader.TokenType != JsonTokenType.PropertyName && + reader.GetString() != "MyInt") + { + throw new JsonException("Wrong property name"); + } + + reader.Read(); + obj.MyInt = reader.GetInt32() - 100; + + reader.Read(); + if (reader.TokenType != JsonTokenType.EndObject) + { + throw new JsonException("No EndObject"); + } + + return obj; + } + + public override void Write(Utf8JsonWriter writer, StructWithCustomConverter value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + writer.WriteNumber(nameof(StructWithCustomConverter.MyInt), value.MyInt + 100); + writer.WriteEndObject(); + } + } + + /// + /// Custom converter that adds\substract 100 from MyIntProperty. + /// + public class CustomConverter_StructWithCustomConverterFactory : JsonConverter + { + public override StructWithCustomConverterFactory Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException("No StartObject"); + } + + StructWithCustomConverterFactory obj = new(); + + reader.Read(); + if (reader.TokenType != JsonTokenType.PropertyName && + reader.GetString() != "MyInt") + { + throw new JsonException("Wrong property name"); + } + + reader.Read(); + obj.MyInt = reader.GetInt32() - 100; + + reader.Read(); + if (reader.TokenType != JsonTokenType.EndObject) + { + throw new JsonException("No EndObject"); + } + + return obj; + } + + public override void Write(Utf8JsonWriter writer, StructWithCustomConverterFactory value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + writer.WriteNumber(nameof(StructWithCustomConverterFactory.MyInt), value.MyInt + 100); + writer.WriteEndObject(); + } + } + + public class CustomConverterFactory : JsonConverterFactory + { + public CustomConverterFactory() + { + } + + public override bool CanConvert(Type typeToConvert) + { + return ( + typeToConvert == typeof(StructWithCustomConverterFactory) || + typeToConvert == typeof(ClassWithCustomConverterFactory)); + } + + public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + if (typeToConvert == typeof(StructWithCustomConverterFactory)) + { + return new CustomConverter_StructWithCustomConverterFactory(); + } + + if (typeToConvert == typeof(ClassWithCustomConverterFactory)) + { + return new CustomConverter_ClassWithCustomConverterFactory(); + } + + throw new InvalidOperationException("Not expected."); + } + } + + [JsonConverter(typeof(CustomConverter_StructWithCustomConverter))] + public struct StructWithCustomConverter + { + public int MyInt { get; set; } + } + + [JsonConverter(typeof(CustomConverterFactory))] + public struct StructWithCustomConverterFactory + { + public int MyInt { get; set; } + } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum EnumWithJsonStringEnumConverter + { + A = 1, + B = 2 + } + + public class ClassWithCustomConverterProperty + { + [JsonConverter(typeof(NestedPocoCustomConverter))] + public NestedPoco Property { get; set; } + + public class NestedPoco + { + public int Value { get; set; } + } + + public class NestedPocoCustomConverter : JsonConverter + { + public override NestedPoco? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => new NestedPoco { Value = reader.GetInt32() }; + public override void Write(Utf8JsonWriter writer, NestedPoco value, JsonSerializerOptions options) => writer.WriteNumberValue(value.Value); + } + } + + public struct StructWithCustomConverterProperty + { + [JsonConverter(typeof(ClassWithCustomConverterProperty.NestedPocoCustomConverter))] + public ClassWithCustomConverterProperty.NestedPoco Property { get; set; } + } + + [JsonConverter(typeof(CustomConverter_StructWithCustomConverter))] // Invalid + public struct StructWithBadCustomConverter + { + public int MyInt { get; set; } + } +} 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 05e3b0e3c0aed..887f65b80da83 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,133 +151,4 @@ public class JsonMessage } internal struct MyStruct { } - - /// - /// Custom converter that adds\substract 100 from MyIntProperty. - /// - public class CustomConverterForClass : JsonConverter - { - public override ClassWithCustomConverter Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (reader.TokenType != JsonTokenType.StartObject) - { - throw new JsonException("No StartObject"); - } - - ClassWithCustomConverter obj = new(); - - reader.Read(); - if (reader.TokenType != JsonTokenType.PropertyName && - reader.GetString() != "MyInt") - { - throw new JsonException("Wrong property name"); - } - - reader.Read(); - obj.MyInt = reader.GetInt32() - 100; - - reader.Read(); - if (reader.TokenType != JsonTokenType.EndObject) - { - throw new JsonException("No EndObject"); - } - - return obj; - } - - public override void Write(Utf8JsonWriter writer, ClassWithCustomConverter value, JsonSerializerOptions options) - { - writer.WriteStartObject(); - writer.WriteNumber(nameof(ClassWithCustomConverter.MyInt), value.MyInt + 100); - writer.WriteEndObject(); - } - } - - [JsonConverter(typeof(CustomConverterForClass))] - public class ClassWithCustomConverter - { - public int MyInt { get; set; } - } - - public class ClassWithCustomConverterProperty - { - [JsonConverter(typeof(NestedPocoCustomConverter))] - public NestedPoco Property { get; set; } - - public class NestedPoco - { - public int Value { get; set; } - } - - public class NestedPocoCustomConverter : JsonConverter - { - public override NestedPoco? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => new NestedPoco { Value = reader.GetInt32() }; - public override void Write(Utf8JsonWriter writer, NestedPoco value, JsonSerializerOptions options) => writer.WriteNumberValue(value.Value); - } - } - - public struct StructWithCustomConverterProperty - { - [JsonConverter(typeof(ClassWithCustomConverterProperty.NestedPocoCustomConverter))] - public ClassWithCustomConverterProperty.NestedPoco Property { get; set; } - } - - [JsonConverter(typeof(CustomConverterForStruct))] // Invalid - public class ClassWithBadCustomConverter - { - public int MyInt { get; set; } - } - - /// - /// Custom converter that adds\substract 100 from MyIntProperty. - /// - public class CustomConverterForStruct : JsonConverter - { - public override StructWithCustomConverter Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (reader.TokenType != JsonTokenType.StartObject) - { - throw new JsonException("No StartObject"); - } - - StructWithCustomConverter obj = new(); - - reader.Read(); - if (reader.TokenType != JsonTokenType.PropertyName && - reader.GetString() != "MyInt") - { - throw new JsonException("Wrong property name"); - } - - reader.Read(); - obj.MyInt = reader.GetInt32() - 100; - - reader.Read(); - if (reader.TokenType != JsonTokenType.EndObject) - { - throw new JsonException("No EndObject"); - } - - return obj; - } - - public override void Write(Utf8JsonWriter writer, StructWithCustomConverter value, JsonSerializerOptions options) - { - writer.WriteStartObject(); - writer.WriteNumber(nameof(StructWithCustomConverter.MyInt), value.MyInt + 100); - writer.WriteEndObject(); - } - } - - [JsonConverter(typeof(CustomConverterForStruct))] - public struct StructWithCustomConverter - { - public int MyInt { get; set; } - } - - [JsonConverter(typeof(CustomConverterForClass))] // Invalid - public struct StructWithBadCustomConverter - { - public int MyInt { get; set; } - } } From 8663e9e58c535a86ef5912975b3dcff28e3996a0 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Thu, 2 Sep 2021 12:03:12 -0500 Subject: [PATCH 2/4] Support property factory converters --- .../gen/JsonSourceGenerator.Emitter.cs | 80 ++++++-- .../gen/JsonSourceGenerator.Parser.cs | 187 +++++++++++++----- .../gen/PropertyGenerationSpec.cs | 2 + .../gen/TypeGenerationSpec.cs | 10 +- 4 files changed, 210 insertions(+), 69 deletions(-) diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index 8a49552328c83..52d89c8300da5 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -19,17 +19,19 @@ public sealed partial class JsonSourceGenerator private sealed partial class Emitter { // Literals in generated source - private const string RuntimeCustomConverterFetchingMethodName = "GetRuntimeProvidedCustomConverter"; - private const string OptionsInstanceVariableName = "Options"; - private const string PropInitMethodNameSuffix = "PropInit"; - private const string CtorParamInitMethodNameSuffix = "CtorParamInit"; - private const string SerializeMethodNameSuffix = "Serialize"; private const string CreateValueInfoMethodName = "CreateValueInfo"; + private const string CtorParamInitMethodNameSuffix = "CtorParamInit"; private const string DefaultOptionsStaticVarName = "s_defaultOptions"; private const string DefaultContextBackingStaticVarName = "s_defaultContext"; - private const string WriterVarName = "writer"; - private const string ValueVarName = "value"; + internal const string GetConverterFromFactoryMethodName = "GetConverterFromFactory"; private const string JsonSerializerContextName = "JsonSerializerContext"; + internal const string JsonContextVarName = "jsonContext"; + internal const string OptionsInstanceVariableName = "Options"; + private const string PropInitMethodNameSuffix = "PropInit"; + private const string RuntimeCustomConverterFetchingMethodName = "GetRuntimeProvidedCustomConverter"; + private const string SerializeMethodNameSuffix = "Serialize"; + private const string ValueVarName = "value"; + private const string WriterVarName = "writer"; private static AssemblyName _assemblyName = typeof(Emitter).Assembly.GetName(); private static readonly string s_generatedCodeAttributeSource = $@" @@ -52,7 +54,7 @@ private sealed partial class Emitter private const string JsonSerializerTypeRef = "global::System.Text.Json.JsonSerializer"; private const string JsonSerializerOptionsTypeRef = "global::System.Text.Json.JsonSerializerOptions"; private const string Utf8JsonWriterTypeRef = "global::System.Text.Json.Utf8JsonWriter"; - private const string JsonConverterTypeRef = "global::System.Text.Json.Serialization.JsonConverter"; + internal const string JsonConverterTypeRef = "global::System.Text.Json.Serialization.JsonConverter"; private const string JsonConverterFactoryTypeRef = "global::System.Text.Json.Serialization.JsonConverterFactory"; private const string JsonIgnoreConditionTypeRef = "global::System.Text.Json.Serialization.JsonIgnoreCondition"; private const string JsonNumberHandlingTypeRef = "global::System.Text.Json.Serialization.JsonNumberHandling"; @@ -99,15 +101,24 @@ public void Emit() { _currentContext = contextGenerationSpec; + bool generateGetConverterMethodForTypes = false; + bool generateGetConverterMethodForProperties = false; + foreach (TypeGenerationSpec typeGenerationSpec in _currentContext.RootSerializableTypes) { GenerateTypeInfo(typeGenerationSpec); + + generateGetConverterMethodForTypes |= typeGenerationSpec.HasTypeFactoryConverter; + generateGetConverterMethodForProperties |= typeGenerationSpec.HasPropertyFactoryConverters; } string contextName = _currentContext.ContextType.Name; // Add root context implementation. - AddSource($"{contextName}.g.cs", GetRootJsonContextImplementation(), isRootContextDef: true); + AddSource( + $"{contextName}.g.cs", + GetRootJsonContextImplementation(generateGetConverterMethodForTypes, generateGetConverterMethodForProperties), + isRootContextDef: true); // Add GetJsonTypeInfo override implementation. AddSource($"{contextName}.GetJsonTypeInfo.g.cs", GetGetTypeInfoImplementation()); @@ -298,6 +309,7 @@ private string GenerateForTypeWithUnknownConverter(TypeGenerationSpec typeMetada {{ // Allow nullable handling to forward to the underlying type's converter. converter = {JsonMetadataServicesTypeRef}.GetNullableConverter<{typeCompilableName}>(this.{typeFriendlyName})!; + converter = (({ JsonConverterFactoryTypeRef })converter).CreateConverter(typeToConvert, { OptionsInstanceVariableName })!; }} else {{ @@ -315,15 +327,6 @@ private string GenerateForTypeWithUnknownConverter(TypeGenerationSpec typeMetada } metadataInitSource.Append($@" - else if (converter is { JsonConverterFactoryTypeRef } factory) - {{ - {JsonConverterTypeRef}? actualConverter = factory.CreateConverter(typeToConvert, { OptionsInstanceVariableName }); - if (actualConverter == null || actualConverter is { JsonConverterFactoryTypeRef }) - {{ - throw new { InvalidOperationExceptionTypeRef }($""JsonConverterFactory '{{factory.GetType()}}' cannot return a 'null' or 'JsonConverterFactory' value.""); - }} - converter = actualConverter; - }} _{typeFriendlyName} = { JsonMetadataServicesTypeRef }.{ GetCreateValueInfoMethodRef(typeCompilableName)} ({ OptionsInstanceVariableName}, converter); "); return GenerateForType(typeMetadata, metadataInitSource.ToString()); @@ -480,7 +483,7 @@ private string GenerateFastPathFuncForEnumerable(TypeGenerationSpec typeGenerati Type elementType = valueTypeGenerationSpec.Type; string? writerMethodToCall = GetWriterMethod(elementType); - + string iterationLogic; string valueToWrite; @@ -634,7 +637,6 @@ private string GenerateForObject(TypeGenerationSpec typeMetadata) private string GeneratePropMetadataInitFunc(TypeGenerationSpec typeGenerationSpec) { const string PropVarName = "properties"; - const string JsonContextVarName = "jsonContext"; List properties = typeGenerationSpec.PropertyGenSpecList!; @@ -1089,7 +1091,9 @@ private string WrapWithCheckForCustomConverterIfRequired(string source, string t }}"; } - private string GetRootJsonContextImplementation() + private string GetRootJsonContextImplementation( + bool generateGetConverterMethodForTypes, + bool generateGetConverterMethodForProperties) { string contextTypeRef = _currentContext.ContextTypeRef; string contextTypeName = _currentContext.ContextType.Name; @@ -1111,6 +1115,16 @@ private string GetRootJsonContextImplementation() {GetFetchLogicForRuntimeSpecifiedCustomConverter()}"); + if (generateGetConverterMethodForProperties) + { + sb.Append(GetFetchLogicForGetCustomConverter_PropertiesWithFactories()); + } + + if (generateGetConverterMethodForProperties || generateGetConverterMethodForTypes) + { + sb.Append(GetFetchLogicForGetCustomConverter_TypesWithFactories()); + } + return sb.ToString(); } @@ -1169,6 +1183,30 @@ private string GetFetchLogicForRuntimeSpecifiedCustomConverter() }}"; } + private string GetFetchLogicForGetCustomConverter_PropertiesWithFactories() + { + return @$" +private {JsonConverterTypeRef} {GetConverterFromFactoryMethodName}({JsonConverterFactoryTypeRef} factory) +{{ + return ({JsonConverterTypeRef}) {GetConverterFromFactoryMethodName}(typeof(T), factory); +}}"; + } + + private string GetFetchLogicForGetCustomConverter_TypesWithFactories() + { + return @$" +private {JsonConverterTypeRef} {GetConverterFromFactoryMethodName}({TypeTypeRef} type, {JsonConverterFactoryTypeRef} factory) +{{ + {JsonConverterTypeRef}? converter = factory.CreateConverter(type, {Emitter.OptionsInstanceVariableName}); + if (converter == null || converter is {JsonConverterFactoryTypeRef}) + {{ + throw new {InvalidOperationExceptionTypeRef}($""The converter '{{factory.GetType()}}' cannot return null or a JsonConverterFactory instance.""); + }} + + return converter; +}}"; + } + private string GetGetTypeInfoImplementation() { StringBuilder sb = new(); diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index cd47bcae0c075..ac2fb2d402e1f 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -25,6 +25,7 @@ private sealed class Parser private const string SystemTextJsonNamespace = "System.Text.Json"; private const string JsonConverterAttributeFullName = "System.Text.Json.Serialization.JsonConverterAttribute"; private const string JsonElementFullName = "System.Text.Json.JsonElement"; + private const string JsonConverterFactoryFullName = "System.Text.Json.Serialization.JsonConverterFactory"; private const string JsonIgnoreAttributeFullName = "System.Text.Json.Serialization.JsonIgnoreAttribute"; private const string JsonIgnoreConditionFullName = "System.Text.Json.Serialization.JsonIgnoreCondition"; private const string JsonIncludeAttributeFullName = "System.Text.Json.Serialization.JsonIncludeAttribute"; @@ -571,6 +572,8 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener string? converterInstatiationLogic = null; bool implementsIJsonOnSerialized = false; bool implementsIJsonOnSerializing = false; + bool hasTypeFactoryConverter = false; + bool hasPropertyFactoryConverters = false; IList attributeDataList = CustomAttributeData.GetCustomAttributes(type); foreach (CustomAttributeData attributeData in attributeDataList) @@ -585,7 +588,11 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener else if (!foundDesignTimeCustomConverter && attributeType.GetCompatibleBaseClass(JsonConverterAttributeFullName) != null) { foundDesignTimeCustomConverter = true; - converterInstatiationLogic = GetConverterInstantiationLogic(attributeData); + converterInstatiationLogic = GetConverterInstantiationLogic( + type, + attributeData, + fromContext: true, + ref hasTypeFactoryConverter); } } @@ -828,7 +835,9 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener PropertyGenerationSpec spec = GetPropertyGenerationSpec(propertyInfo, isVirtual, generationMode); CacheMember(spec, ref propGenSpecList, ref ignoredMembers); + propertyOrderSpecified |= spec.Order != 0; + hasPropertyFactoryConverters |= spec.HasFactoryConverter; } foreach (FieldInfo fieldInfo in currentType.GetFields(bindingFlags)) @@ -840,7 +849,9 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener PropertyGenerationSpec spec = GetPropertyGenerationSpec(fieldInfo, isVirtual: false, generationMode); CacheMember(spec, ref propGenSpecList, ref ignoredMembers); + propertyOrderSpecified |= spec.Order != 0; + hasPropertyFactoryConverters |= spec.HasFactoryConverter; } } @@ -865,7 +876,9 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener nullableUnderlyingTypeMetadata: nullableUnderlyingTypeGenSpec, converterInstatiationLogic, implementsIJsonOnSerialized, - implementsIJsonOnSerializing); + implementsIJsonOnSerializing, + hasTypeFactoryConverter, + hasPropertyFactoryConverters); return typeMetadata; } @@ -905,16 +918,95 @@ private static bool PropertyIsOverridenAndIgnored( private PropertyGenerationSpec GetPropertyGenerationSpec(MemberInfo memberInfo, bool isVirtual, JsonSourceGenerationMode generationMode) { + Type memberCLRType = GetMemberClrType(memberInfo); IList attributeDataList = CustomAttributeData.GetCustomAttributes(memberInfo); - bool hasJsonInclude = false; - JsonIgnoreCondition? ignoreCondition = null; - JsonNumberHandling? numberHandling = null; - string? jsonPropertyName = null; + ProcessCustomAttributes( + attributeDataList, + memberCLRType, + out bool hasJsonInclude, + out string? jsonPropertyName, + out JsonIgnoreCondition? ignoreCondition, + out JsonNumberHandling? numberHandling, + out string? converterInstantiationLogic, + out int order, + out bool hasFactoryConverter); + + ProcessMember( + memberInfo, + memberCLRType, + hasJsonInclude, + out bool isReadOnly, + out bool isPublic, + out bool canUseGetter, + out bool canUseSetter, + out bool getterIsVirtual, + out bool setterIsVirtual); + + string clrName = memberInfo.Name; + string runtimePropertyName = DetermineRuntimePropName(clrName, jsonPropertyName, _currentContextNamingPolicy); + string propertyNameVarName = DeterminePropNameIdentifier(runtimePropertyName); + + return new PropertyGenerationSpec + { + ClrName = clrName, + IsProperty = memberInfo.MemberType == MemberTypes.Property, + IsPublic = isPublic, + IsVirtual = isVirtual, + JsonPropertyName = jsonPropertyName, + RuntimePropertyName = runtimePropertyName, + PropertyNameVarName = propertyNameVarName, + IsReadOnly = isReadOnly, + CanUseGetter = canUseGetter, + CanUseSetter = canUseSetter, + GetterIsVirtual = getterIsVirtual, + SetterIsVirtual = setterIsVirtual, + DefaultIgnoreCondition = ignoreCondition, + NumberHandling = numberHandling, + Order = order, + HasJsonInclude = hasJsonInclude, + TypeGenerationSpec = GetOrAddTypeGenerationSpec(memberCLRType, generationMode), + DeclaringTypeRef = memberInfo.DeclaringType.GetCompilableName(), + ConverterInstantiationLogic = converterInstantiationLogic, + HasFactoryConverter = hasFactoryConverter + }; + } + + private Type GetMemberClrType(MemberInfo memberInfo) + { + if (memberInfo is PropertyInfo propertyInfo) + { + return propertyInfo.PropertyType; + } + + if (memberInfo is FieldInfo fieldInfo) + { + return fieldInfo.FieldType; + } + + throw new InvalidOperationException(); + } + + private void ProcessCustomAttributes( + IList attributeDataList, + Type memberCLRType, + out bool hasJsonInclude, + out string? jsonPropertyName, + out JsonIgnoreCondition? ignoreCondition, + out JsonNumberHandling? numberHandling, + out string? converterInstantiationLogic, + out int order, + out bool hasFactoryConverter) + { + hasJsonInclude = false; + jsonPropertyName = null; + ignoreCondition = default; + numberHandling = default; + converterInstantiationLogic = null; + order = 0; bool foundDesignTimeCustomConverter = false; - string? converterInstantiationLogic = null; - int order = 0; + hasFactoryConverter = false; foreach (CustomAttributeData attributeData in attributeDataList) { @@ -923,7 +1015,11 @@ private PropertyGenerationSpec GetPropertyGenerationSpec(MemberInfo memberInfo, if (!foundDesignTimeCustomConverter && attributeType.GetCompatibleBaseClass(JsonConverterAttributeFullName) != null) { foundDesignTimeCustomConverter = true; - converterInstantiationLogic = GetConverterInstantiationLogic(attributeData); + converterInstantiationLogic = GetConverterInstantiationLogic( + memberCLRType, + attributeData, + fromContext: false, + ref hasFactoryConverter); } else if (attributeType.Assembly.FullName == SystemTextJsonNamespace) { @@ -974,21 +1070,29 @@ private PropertyGenerationSpec GetPropertyGenerationSpec(MemberInfo memberInfo, } } } + } - Type memberCLRType; - bool isReadOnly; - bool isPublic = false; - bool canUseGetter = false; - bool canUseSetter = false; - bool getterIsVirtual = false; - bool setterIsVirtual = false; + private static void ProcessMember( + MemberInfo memberInfo, + Type memberClrType, + bool hasJsonInclude, + out bool isReadOnly, + out bool isPublic, + out bool canUseGetter, + out bool canUseSetter, + out bool getterIsVirtual, + out bool setterIsVirtual) + { + isPublic = false; + canUseGetter = false; + canUseSetter = false; + getterIsVirtual = false; + setterIsVirtual = false; switch (memberInfo) { case PropertyInfo propertyInfo: { - memberCLRType = propertyInfo.PropertyType; - MethodInfo? getMethod = propertyInfo.GetMethod; MethodInfo? setMethod = propertyInfo.SetMethod; @@ -1031,7 +1135,6 @@ private PropertyGenerationSpec GetPropertyGenerationSpec(MemberInfo memberInfo, break; case FieldInfo fieldInfo: { - memberCLRType = fieldInfo.FieldType; isPublic = fieldInfo.IsPublic; isReadOnly = fieldInfo.IsInitOnly; @@ -1045,39 +1148,15 @@ private PropertyGenerationSpec GetPropertyGenerationSpec(MemberInfo memberInfo, default: throw new InvalidOperationException(); } - - string clrName = memberInfo.Name; - string runtimePropertyName = DetermineRuntimePropName(clrName, jsonPropertyName, _currentContextNamingPolicy); - string propertyNameVarName = DeterminePropNameIdentifier(runtimePropertyName); - - return new PropertyGenerationSpec - { - ClrName = clrName, - IsProperty = memberInfo.MemberType == MemberTypes.Property, - IsPublic = isPublic, - IsVirtual = isVirtual, - JsonPropertyName = jsonPropertyName, - RuntimePropertyName = runtimePropertyName, - PropertyNameVarName = propertyNameVarName, - IsReadOnly = isReadOnly, - CanUseGetter = canUseGetter, - CanUseSetter = canUseSetter, - GetterIsVirtual = getterIsVirtual, - SetterIsVirtual = setterIsVirtual, - DefaultIgnoreCondition = ignoreCondition, - NumberHandling = numberHandling, - Order = order, - HasJsonInclude = hasJsonInclude, - TypeGenerationSpec = GetOrAddTypeGenerationSpec(memberCLRType, generationMode), - DeclaringTypeRef = memberInfo.DeclaringType.GetCompilableName(), - ConverterInstantiationLogic = converterInstantiationLogic - }; } private static bool PropertyAccessorCanBeReferenced(MethodInfo? accessor) => accessor != null && (accessor.IsPublic || accessor.IsAssembly); - private string? GetConverterInstantiationLogic(CustomAttributeData attributeData) + private string? GetConverterInstantiationLogic( + Type type, CustomAttributeData attributeData, + bool fromContext, + ref bool hasFactoryConverter) { if (attributeData.AttributeType.FullName != JsonConverterAttributeFullName) { @@ -1092,6 +1171,20 @@ private static bool PropertyAccessorCanBeReferenced(MethodInfo? accessor) return null; } + if (converterType.GetCompatibleBaseClass(JsonConverterFactoryFullName) != null) + { + hasFactoryConverter = true; + + if (fromContext) + { + return $"this.{Emitter.GetConverterFromFactoryMethodName}(typeof({type.GetCompilableName()}), new {converterType.GetCompilableName()}())"; + } + else + { + return $"{Emitter.JsonContextVarName}.{Emitter.GetConverterFromFactoryMethodName}<{type.GetCompilableName()}>(new {converterType.GetCompilableName()}())"; + } + } + return $"new {converterType.GetCompilableName()}()"; } diff --git a/src/libraries/System.Text.Json/gen/PropertyGenerationSpec.cs b/src/libraries/System.Text.Json/gen/PropertyGenerationSpec.cs index 74740cc65af86..886381a22a8be 100644 --- a/src/libraries/System.Text.Json/gen/PropertyGenerationSpec.cs +++ b/src/libraries/System.Text.Json/gen/PropertyGenerationSpec.cs @@ -89,5 +89,7 @@ internal sealed class PropertyGenerationSpec /// Source code to instantiate design-time specified custom converter. /// public string? ConverterInstantiationLogic { get; init; } + + public bool HasFactoryConverter { get; init; } } } diff --git a/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs b/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs index 21648615f21fd..be51f949d4fbd 100644 --- a/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs +++ b/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs @@ -60,6 +60,10 @@ internal class TypeGenerationSpec public string? ConverterInstantiationLogic { get; private set; } + // Only generate certain helper methods if necessary. + public bool HasPropertyFactoryConverters { get; private set; } + public bool HasTypeFactoryConverter { get; private set; } + public string FastPathSerializeMethodName { get @@ -107,7 +111,9 @@ public void Initialize( TypeGenerationSpec? nullableUnderlyingTypeMetadata, string? converterInstantiationLogic, bool implementsIJsonOnSerialized, - bool implementsIJsonOnSerializing) + bool implementsIJsonOnSerializing, + bool hasTypeFactoryConverter, + bool hasPropertyFactoryConverters) { GenerationMode = generationMode; TypeRef = type.GetCompilableName(); @@ -127,6 +133,8 @@ public void Initialize( ConverterInstantiationLogic = converterInstantiationLogic; ImplementsIJsonOnSerialized = implementsIJsonOnSerialized; ImplementsIJsonOnSerializing = implementsIJsonOnSerializing; + HasTypeFactoryConverter = hasTypeFactoryConverter; + HasPropertyFactoryConverters = hasPropertyFactoryConverters; } public bool TryFilterSerializableProps( From faec20d83a2d11bdc160bcf50bff228c1679b87a Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Thu, 2 Sep 2021 14:55:29 -0500 Subject: [PATCH 3/4] Fixup merge ordering; add tests --- .../gen/JsonSourceGenerator.Emitter.cs | 6 +- .../gen/JsonSourceGenerator.Parser.cs | 8 +-- .../ContextClasses.cs | 7 ++- .../MetadataAndSerializationContextTests.cs | 15 +++-- .../MetadataContextTests.cs | 32 ++++++---- .../MixedModeContextTests.cs | 15 +++-- .../RealWorldContextTests.cs | 62 +++++++++++++++++++ .../SerializationContextTests.cs | 34 +++++----- .../TestClasses.CustomConverters.cs | 57 ++++++++++------- 9 files changed, 164 insertions(+), 72 deletions(-) diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index 52d89c8300da5..b2b3488b03b3b 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -26,7 +26,7 @@ private sealed partial class Emitter internal const string GetConverterFromFactoryMethodName = "GetConverterFromFactory"; private const string JsonSerializerContextName = "JsonSerializerContext"; internal const string JsonContextVarName = "jsonContext"; - internal const string OptionsInstanceVariableName = "Options"; + private const string OptionsInstanceVariableName = "Options"; private const string PropInitMethodNameSuffix = "PropInit"; private const string RuntimeCustomConverterFetchingMethodName = "GetRuntimeProvidedCustomConverter"; private const string SerializeMethodNameSuffix = "Serialize"; @@ -54,7 +54,7 @@ private sealed partial class Emitter private const string JsonSerializerTypeRef = "global::System.Text.Json.JsonSerializer"; private const string JsonSerializerOptionsTypeRef = "global::System.Text.Json.JsonSerializerOptions"; private const string Utf8JsonWriterTypeRef = "global::System.Text.Json.Utf8JsonWriter"; - internal const string JsonConverterTypeRef = "global::System.Text.Json.Serialization.JsonConverter"; + private const string JsonConverterTypeRef = "global::System.Text.Json.Serialization.JsonConverter"; private const string JsonConverterFactoryTypeRef = "global::System.Text.Json.Serialization.JsonConverterFactory"; private const string JsonIgnoreConditionTypeRef = "global::System.Text.Json.Serialization.JsonIgnoreCondition"; private const string JsonNumberHandlingTypeRef = "global::System.Text.Json.Serialization.JsonNumberHandling"; @@ -1186,6 +1186,7 @@ private string GetFetchLogicForRuntimeSpecifiedCustomConverter() private string GetFetchLogicForGetCustomConverter_PropertiesWithFactories() { return @$" + private {JsonConverterTypeRef} {GetConverterFromFactoryMethodName}({JsonConverterFactoryTypeRef} factory) {{ return ({JsonConverterTypeRef}) {GetConverterFromFactoryMethodName}(typeof(T), factory); @@ -1195,6 +1196,7 @@ private string GetFetchLogicForGetCustomConverter_PropertiesWithFactories() private string GetFetchLogicForGetCustomConverter_TypesWithFactories() { return @$" + private {JsonConverterTypeRef} {GetConverterFromFactoryMethodName}({TypeTypeRef} type, {JsonConverterFactoryTypeRef} factory) {{ {JsonConverterTypeRef}? converter = factory.CreateConverter(type, {Emitter.OptionsInstanceVariableName}); diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index ac2fb2d402e1f..76c30b3c89bed 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -591,7 +591,7 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener converterInstatiationLogic = GetConverterInstantiationLogic( type, attributeData, - fromContext: true, + forType: true, ref hasTypeFactoryConverter); } } @@ -1018,7 +1018,7 @@ private void ProcessCustomAttributes( converterInstantiationLogic = GetConverterInstantiationLogic( memberCLRType, attributeData, - fromContext: false, + forType: false, ref hasFactoryConverter); } else if (attributeType.Assembly.FullName == SystemTextJsonNamespace) @@ -1155,7 +1155,7 @@ private static bool PropertyAccessorCanBeReferenced(MethodInfo? accessor) private string? GetConverterInstantiationLogic( Type type, CustomAttributeData attributeData, - bool fromContext, + bool forType, // whether for a type or a property ref bool hasFactoryConverter) { if (attributeData.AttributeType.FullName != JsonConverterAttributeFullName) @@ -1175,7 +1175,7 @@ private static bool PropertyAccessorCanBeReferenced(MethodInfo? accessor) { hasFactoryConverter = true; - if (fromContext) + if (forType) { return $"this.{Emitter.GetConverterFromFactoryMethodName}(typeof({type.GetCompilableName()}), new {converterType.GetCompilableName()}())"; } 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 04b2a39f19fb8..47bfe5a63a880 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 @@ -32,12 +32,13 @@ public interface ITestContext public JsonTypeInfo String { get; } public JsonTypeInfo ClassWithEnumAndNullable { get; } public JsonTypeInfo ClassWithCustomConverter { get; } - public JsonTypeInfo ClassWithCustomConverterFactory { get; } public JsonTypeInfo StructWithCustomConverter { get; } + public JsonTypeInfo ClassWithCustomConverterFactory { get; } + public JsonTypeInfo StructWithCustomConverterFactory { get; } public JsonTypeInfo ClassWithCustomConverterProperty { get; } public JsonTypeInfo StructWithCustomConverterProperty { get; } - public JsonTypeInfo StructWithCustomConverterFactory { get; } - public JsonTypeInfo EnumWithJsonStringEnumConverter { get; } + public JsonTypeInfo ClassWithCustomConverterPropertyFactory { get; } + public JsonTypeInfo StructWithCustomConverterPropertyFactory { get; } public JsonTypeInfo ClassWithBadCustomConverter { get; } public JsonTypeInfo StructWithBadCustomConverter { get; } } 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 52a9d6936e679..201ef07e5253b 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 @@ -28,12 +28,13 @@ namespace System.Text.Json.SourceGeneration.Tests [JsonSerializable(typeof(string))] [JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable))] [JsonSerializable(typeof(ClassWithCustomConverter))] - [JsonSerializable(typeof(ClassWithCustomConverterFactory))] [JsonSerializable(typeof(StructWithCustomConverter))] + [JsonSerializable(typeof(ClassWithCustomConverterFactory))] + [JsonSerializable(typeof(StructWithCustomConverterFactory))] [JsonSerializable(typeof(ClassWithCustomConverterProperty))] [JsonSerializable(typeof(StructWithCustomConverterProperty))] - [JsonSerializable(typeof(StructWithCustomConverterFactory))] - [JsonSerializable(typeof(EnumWithJsonStringEnumConverter))] + [JsonSerializable(typeof(ClassWithCustomConverterPropertyFactory))] + [JsonSerializable(typeof(StructWithCustomConverterPropertyFactory))] [JsonSerializable(typeof(ClassWithBadCustomConverter))] [JsonSerializable(typeof(StructWithBadCustomConverter))] internal partial class MetadataAndSerializationContext : JsonSerializerContext, ITestContext @@ -66,15 +67,17 @@ public override void EnsureFastPathGeneratedAsExpected() Assert.NotNull(MetadataAndSerializationContext.Default.MyNestedClass.Serialize); Assert.NotNull(MetadataAndSerializationContext.Default.MyNestedNestedClass.Serialize); Assert.Null(MetadataAndSerializationContext.Default.ObjectArray.Serialize); + Assert.Null(MetadataAndSerializationContext.Default.SampleEnum.Serialize); Assert.Null(MetadataAndSerializationContext.Default.String.Serialize); Assert.NotNull(MetadataAndSerializationContext.Default.ClassWithEnumAndNullable.Serialize); Assert.NotNull(MetadataAndSerializationContext.Default.ClassWithCustomConverter); - Assert.NotNull(MetadataAndSerializationContext.Default.ClassWithCustomConverterFactory); Assert.NotNull(MetadataAndSerializationContext.Default.StructWithCustomConverter); + Assert.NotNull(MetadataAndSerializationContext.Default.ClassWithCustomConverterFactory); + Assert.NotNull(MetadataAndSerializationContext.Default.StructWithCustomConverterFactory); Assert.NotNull(MetadataAndSerializationContext.Default.ClassWithCustomConverterProperty); Assert.NotNull(MetadataAndSerializationContext.Default.StructWithCustomConverterProperty); - Assert.NotNull(MetadataAndSerializationContext.Default.StructWithCustomConverterFactory); - Assert.NotNull(MetadataAndSerializationContext.Default.EnumWithJsonStringEnumConverter); + Assert.NotNull(MetadataAndSerializationContext.Default.ClassWithCustomConverterPropertyFactory); + Assert.NotNull(MetadataAndSerializationContext.Default.StructWithCustomConverterPropertyFactory); Assert.Throws(() => MetadataAndSerializationContext.Default.ClassWithBadCustomConverter); Assert.Throws(() => MetadataAndSerializationContext.Default.StructWithBadCustomConverter); } 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 618f6907ca149..6e6d7b18159bc 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 @@ -27,14 +27,13 @@ namespace System.Text.Json.SourceGeneration.Tests [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.Metadata)] [JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Metadata)] [JsonSerializable(typeof(ClassWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata)] - [JsonSerializable(typeof(ClassWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Metadata)] - [JsonSerializable(typeof(StructWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata)] - [JsonSerializable(typeof(ClassWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata)] [JsonSerializable(typeof(StructWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(ClassWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(StructWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Metadata)] [JsonSerializable(typeof(ClassWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Metadata)] [JsonSerializable(typeof(StructWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Metadata)] - [JsonSerializable(typeof(StructWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Metadata)] - [JsonSerializable(typeof(EnumWithJsonStringEnumConverter), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(ClassWithCustomConverterPropertyFactory), GenerationMode = JsonSourceGenerationMode.Metadata)] + [JsonSerializable(typeof(StructWithCustomConverterPropertyFactory), GenerationMode = JsonSourceGenerationMode.Metadata)] [JsonSerializable(typeof(ClassWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata)] [JsonSerializable(typeof(StructWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata)] internal partial class MetadataWithPerTypeAttributeContext : JsonSerializerContext, ITestContext @@ -65,13 +64,17 @@ public override void EnsureFastPathGeneratedAsExpected() Assert.Null(MetadataWithPerTypeAttributeContext.Default.MyNestedClass.Serialize); Assert.Null(MetadataWithPerTypeAttributeContext.Default.MyNestedNestedClass.Serialize); Assert.Null(MetadataWithPerTypeAttributeContext.Default.ObjectArray.Serialize); + Assert.Null(MetadataWithPerTypeAttributeContext.Default.SampleEnum.Serialize); Assert.Null(MetadataWithPerTypeAttributeContext.Default.String.Serialize); Assert.Null(MetadataWithPerTypeAttributeContext.Default.ClassWithEnumAndNullable.Serialize); Assert.Null(MetadataWithPerTypeAttributeContext.Default.ClassWithCustomConverter.Serialize); - Assert.Null(MetadataWithPerTypeAttributeContext.Default.ClassWithCustomConverterFactory.Serialize); Assert.Null(MetadataWithPerTypeAttributeContext.Default.StructWithCustomConverter.Serialize); + Assert.Null(MetadataWithPerTypeAttributeContext.Default.ClassWithCustomConverterFactory.Serialize); Assert.Null(MetadataWithPerTypeAttributeContext.Default.StructWithCustomConverterFactory.Serialize); - Assert.Null(MetadataWithPerTypeAttributeContext.Default.EnumWithJsonStringEnumConverter.Serialize); + Assert.Null(MetadataWithPerTypeAttributeContext.Default.ClassWithCustomConverterProperty.Serialize); + Assert.Null(MetadataWithPerTypeAttributeContext.Default.StructWithCustomConverterProperty.Serialize); + Assert.Null(MetadataWithPerTypeAttributeContext.Default.ClassWithCustomConverterPropertyFactory.Serialize); + Assert.Null(MetadataWithPerTypeAttributeContext.Default.StructWithCustomConverterPropertyFactory.Serialize); Assert.Throws(() => MetadataWithPerTypeAttributeContext.Default.ClassWithBadCustomConverter.Serialize); Assert.Throws(() => MetadataWithPerTypeAttributeContext.Default.StructWithBadCustomConverter.Serialize); } @@ -99,12 +102,13 @@ public override void EnsureFastPathGeneratedAsExpected() [JsonSerializable(typeof(string))] [JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable))] [JsonSerializable(typeof(ClassWithCustomConverter))] - [JsonSerializable(typeof(ClassWithCustomConverterFactory))] [JsonSerializable(typeof(StructWithCustomConverter))] + [JsonSerializable(typeof(ClassWithCustomConverterFactory))] + [JsonSerializable(typeof(StructWithCustomConverterFactory))] [JsonSerializable(typeof(ClassWithCustomConverterProperty))] [JsonSerializable(typeof(StructWithCustomConverterProperty))] - [JsonSerializable(typeof(StructWithCustomConverterFactory))] - [JsonSerializable(typeof(EnumWithJsonStringEnumConverter))] + [JsonSerializable(typeof(ClassWithCustomConverterPropertyFactory))] + [JsonSerializable(typeof(StructWithCustomConverterPropertyFactory))] [JsonSerializable(typeof(ClassWithBadCustomConverter))] [JsonSerializable(typeof(StructWithBadCustomConverter))] internal partial class MetadataContext : JsonSerializerContext, ITestContext @@ -137,15 +141,17 @@ public override void EnsureFastPathGeneratedAsExpected() Assert.Null(MetadataContext.Default.MyNestedClass.Serialize); Assert.Null(MetadataContext.Default.MyNestedNestedClass.Serialize); Assert.Null(MetadataContext.Default.ObjectArray.Serialize); + Assert.Null(MetadataContext.Default.SampleEnum.Serialize); Assert.Null(MetadataContext.Default.String.Serialize); Assert.Null(MetadataContext.Default.ClassWithEnumAndNullable.Serialize); Assert.Null(MetadataContext.Default.ClassWithCustomConverter.Serialize); - Assert.Null(MetadataContext.Default.ClassWithCustomConverterFactory.Serialize); Assert.Null(MetadataContext.Default.StructWithCustomConverter.Serialize); + Assert.Null(MetadataContext.Default.ClassWithCustomConverterFactory.Serialize); + Assert.Null(MetadataContext.Default.StructWithCustomConverterFactory.Serialize); Assert.Null(MetadataContext.Default.ClassWithCustomConverterProperty.Serialize); Assert.Null(MetadataContext.Default.StructWithCustomConverterProperty.Serialize); - Assert.Null(MetadataContext.Default.StructWithCustomConverterFactory.Serialize); - Assert.Null(MetadataContext.Default.EnumWithJsonStringEnumConverter.Serialize); + Assert.Null(MetadataContext.Default.ClassWithCustomConverterPropertyFactory.Serialize); + Assert.Null(MetadataContext.Default.StructWithCustomConverterPropertyFactory.Serialize); Assert.Throws(() => MetadataContext.Default.ClassWithBadCustomConverter.Serialize); Assert.Throws(() => MetadataContext.Default.StructWithBadCustomConverter.Serialize); } 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 0e0898cf79e7c..ae0c2897daa98 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 @@ -27,12 +27,13 @@ namespace System.Text.Json.SourceGeneration.Tests [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(ClassWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(ClassWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(StructWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(ClassWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(StructWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(ClassWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(StructWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(StructWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(EnumWithJsonStringEnumConverter), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(ClassWithCustomConverterPropertyFactory), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(StructWithCustomConverterPropertyFactory), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(ClassWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(StructWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)] internal partial class MixedModeContext : JsonSerializerContext, ITestContext @@ -64,15 +65,17 @@ public override void EnsureFastPathGeneratedAsExpected() Assert.NotNull(MixedModeContext.Default.MyNestedClass.Serialize); Assert.NotNull(MixedModeContext.Default.MyNestedNestedClass.Serialize); Assert.Null(MixedModeContext.Default.ObjectArray.Serialize); + Assert.Null(MixedModeContext.Default.SampleEnum.Serialize); Assert.Null(MixedModeContext.Default.String.Serialize); Assert.NotNull(MixedModeContext.Default.ClassWithEnumAndNullable.Serialize); Assert.Null(MixedModeContext.Default.ClassWithCustomConverter.Serialize); - Assert.Null(MixedModeContext.Default.ClassWithCustomConverterFactory.Serialize); Assert.Null(MixedModeContext.Default.StructWithCustomConverter.Serialize); + Assert.Null(MixedModeContext.Default.ClassWithCustomConverterFactory.Serialize); + Assert.Null(MixedModeContext.Default.StructWithCustomConverterFactory.Serialize); Assert.Null(MixedModeContext.Default.ClassWithCustomConverterProperty.Serialize); Assert.Null(MixedModeContext.Default.StructWithCustomConverterProperty.Serialize); - Assert.Null(MixedModeContext.Default.StructWithCustomConverterFactory.Serialize); - Assert.Null(MixedModeContext.Default.EnumWithJsonStringEnumConverter.Serialize); + Assert.Null(MixedModeContext.Default.ClassWithCustomConverterPropertyFactory.Serialize); + Assert.Null(MixedModeContext.Default.StructWithCustomConverterPropertyFactory.Serialize); Assert.Throws(() => MixedModeContext.Default.ClassWithBadCustomConverter.Serialize); Assert.Throws(() => MixedModeContext.Default.StructWithBadCustomConverter.Serialize); } 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 19d0143ca4a62..fef2dddc0fec5 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 @@ -215,6 +215,68 @@ public virtual void RoundtripWithCustomConverterProperty_Struct() Assert.Equal(42, obj.Property.Value); } + [Fact] + public virtual void RoundTripWithCustomPropertyConverterFactory_Class() + { + const string Json = "{\"MyEnum\":\"A\"}"; + + ClassWithCustomConverterPropertyFactory obj = new() + { + MyEnum = SampleEnum.A + }; + + if (DefaultContext.JsonSourceGenerationMode == JsonSourceGenerationMode.Serialization) + { + Assert.Throws(() => JsonSerializer.Serialize(obj, DefaultContext.ClassWithCustomConverterPropertyFactory)); + } + else + { + string json = JsonSerializer.Serialize(obj, DefaultContext.ClassWithCustomConverterPropertyFactory); + Assert.Equal(Json, json); + } + + if (DefaultContext.JsonSourceGenerationMode == JsonSourceGenerationMode.Serialization) + { + Assert.Throws(() => JsonSerializer.Serialize(obj, DefaultContext.ClassWithCustomConverterPropertyFactory)); + } + else + { + obj = JsonSerializer.Deserialize(Json, DefaultContext.ClassWithCustomConverterPropertyFactory); + Assert.Equal(SampleEnum.A, obj.MyEnum); + } + } + + [Fact] + public virtual void RoundTripWithCustomPropertyConverterFactory_Struct() + { + const string Json = "{\"MyEnum\":\"A\"}"; + + StructWithCustomConverterPropertyFactory obj = new() + { + MyEnum = SampleEnum.A + }; + + if (DefaultContext.JsonSourceGenerationMode == JsonSourceGenerationMode.Serialization) + { + Assert.Throws(() => JsonSerializer.Serialize(obj, DefaultContext.StructWithCustomConverterPropertyFactory)); + } + else + { + string json = JsonSerializer.Serialize(obj, DefaultContext.StructWithCustomConverterPropertyFactory); + Assert.Equal(Json, json); + } + + if (DefaultContext.JsonSourceGenerationMode == JsonSourceGenerationMode.Serialization) + { + Assert.Throws(() => JsonSerializer.Serialize(obj, DefaultContext.StructWithCustomConverterPropertyFactory)); + } + else + { + obj = JsonSerializer.Deserialize(Json, DefaultContext.StructWithCustomConverterPropertyFactory); + Assert.Equal(SampleEnum.A, obj.MyEnum); + } + } + [Fact] public virtual void BadCustomConverter_Class() { 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 02e1370a62119..501d671f9863b 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 @@ -28,12 +28,13 @@ namespace System.Text.Json.SourceGeneration.Tests [JsonSerializable(typeof(string))] [JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable))] [JsonSerializable(typeof(ClassWithCustomConverter))] - [JsonSerializable(typeof(ClassWithCustomConverterFactory))] [JsonSerializable(typeof(StructWithCustomConverter))] + [JsonSerializable(typeof(ClassWithCustomConverterFactory))] + [JsonSerializable(typeof(StructWithCustomConverterFactory))] [JsonSerializable(typeof(ClassWithCustomConverterProperty))] [JsonSerializable(typeof(StructWithCustomConverterProperty))] - [JsonSerializable(typeof(StructWithCustomConverterFactory))] - [JsonSerializable(typeof(EnumWithJsonStringEnumConverter))] + [JsonSerializable(typeof(ClassWithCustomConverterPropertyFactory))] + [JsonSerializable(typeof(StructWithCustomConverterPropertyFactory))] [JsonSerializable(typeof(ClassWithBadCustomConverter))] [JsonSerializable(typeof(StructWithBadCustomConverter))] internal partial class SerializationContext : JsonSerializerContext, ITestContext @@ -62,12 +63,13 @@ internal partial class SerializationContext : JsonSerializerContext, ITestContex [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(ClassWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(ClassWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(StructWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(ClassWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(StructWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(ClassWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(StructWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(StructWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(EnumWithJsonStringEnumConverter), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(ClassWithCustomConverterPropertyFactory), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(StructWithCustomConverterPropertyFactory), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(ClassWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(StructWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)] internal partial class SerializationWithPerTypeAttributeContext : JsonSerializerContext, ITestContext @@ -97,12 +99,13 @@ internal partial class SerializationWithPerTypeAttributeContext : JsonSerializer [JsonSerializable(typeof(string), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(ClassWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(ClassWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(StructWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(ClassWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(StructWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(ClassWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(StructWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(StructWithCustomConverterFactory), GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(EnumWithJsonStringEnumConverter), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(ClassWithCustomConverterPropertyFactory), GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(StructWithCustomConverterPropertyFactory), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(ClassWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)] [JsonSerializable(typeof(StructWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)] internal partial class SerializationContextWithCamelCase : JsonSerializerContext, ITestContext @@ -143,12 +146,11 @@ public override void EnsureFastPathGeneratedAsExpected() Assert.Null(SerializationContext.Default.String.Serialize); Assert.NotNull(SerializationContext.Default.ClassWithEnumAndNullable.Serialize); Assert.Null(SerializationContext.Default.ClassWithCustomConverter.Serialize); - Assert.Null(SerializationContext.Default.ClassWithCustomConverterFactory.Serialize); Assert.Null(SerializationContext.Default.StructWithCustomConverter.Serialize); + Assert.Null(SerializationContext.Default.ClassWithCustomConverterFactory.Serialize); + Assert.Null(SerializationContext.Default.StructWithCustomConverterFactory.Serialize); Assert.Null(SerializationContext.Default.ClassWithCustomConverterProperty.Serialize); Assert.Null(SerializationContext.Default.StructWithCustomConverterProperty.Serialize); - Assert.Null(SerializationContext.Default.StructWithCustomConverterFactory.Serialize); - Assert.Null(SerializationContext.Default.EnumWithJsonStringEnumConverter.Serialize); Assert.Throws(() => SerializationContext.Default.ClassWithBadCustomConverter.Serialize); Assert.Throws(() => SerializationContext.Default.StructWithBadCustomConverter.Serialize); } @@ -407,15 +409,17 @@ public override void EnsureFastPathGeneratedAsExpected() Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.MyNestedClass.Serialize); Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.MyNestedNestedClass.Serialize); Assert.Null(SerializationWithPerTypeAttributeContext.Default.ObjectArray.Serialize); + Assert.Null(SerializationWithPerTypeAttributeContext.Default.SampleEnum.Serialize); Assert.Null(SerializationWithPerTypeAttributeContext.Default.String.Serialize); Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.ClassWithEnumAndNullable.Serialize); Assert.Null(SerializationWithPerTypeAttributeContext.Default.ClassWithCustomConverter.Serialize); - Assert.Null(SerializationWithPerTypeAttributeContext.Default.ClassWithCustomConverterFactory.Serialize); Assert.Null(SerializationWithPerTypeAttributeContext.Default.StructWithCustomConverter.Serialize); + Assert.Null(SerializationWithPerTypeAttributeContext.Default.ClassWithCustomConverterFactory.Serialize); + Assert.Null(SerializationWithPerTypeAttributeContext.Default.StructWithCustomConverterFactory.Serialize); Assert.Null(SerializationWithPerTypeAttributeContext.Default.ClassWithCustomConverterProperty.Serialize); Assert.Null(SerializationWithPerTypeAttributeContext.Default.StructWithCustomConverterProperty.Serialize); - Assert.Null(SerializationWithPerTypeAttributeContext.Default.StructWithCustomConverterFactory.Serialize); - Assert.Null(SerializationWithPerTypeAttributeContext.Default.EnumWithJsonStringEnumConverter.Serialize); + Assert.Null(SerializationWithPerTypeAttributeContext.Default.ClassWithCustomConverterPropertyFactory.Serialize); + Assert.Null(SerializationWithPerTypeAttributeContext.Default.StructWithCustomConverterPropertyFactory.Serialize); Assert.Throws(() => SerializationWithPerTypeAttributeContext.Default.ClassWithBadCustomConverter.Serialize); Assert.Throws(() => SerializationWithPerTypeAttributeContext.Default.StructWithBadCustomConverter.Serialize); } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.CustomConverters.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.CustomConverters.cs index 490dafee85714..27cf7fb55586e 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.CustomConverters.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.CustomConverters.cs @@ -87,24 +87,6 @@ public override void Write(Utf8JsonWriter writer, ClassWithCustomConverterFactor } } - [JsonConverter(typeof(CustomConverter_ClassWithCustomConverter))] - public class ClassWithCustomConverter - { - public int MyInt { get; set; } - } - - [JsonConverter(typeof(CustomConverterFactory))] - public class ClassWithCustomConverterFactory - { - public int MyInt { get; set; } - } - - [JsonConverter(typeof(CustomConverter_StructWithCustomConverter))] // Invalid - public class ClassWithBadCustomConverter - { - public int MyInt { get; set; } - } - /// /// Custom converter that adds\substract 100 from MyIntProperty. /// @@ -216,6 +198,12 @@ public override bool CanConvert(Type typeToConvert) } } + [JsonConverter(typeof(CustomConverter_ClassWithCustomConverter))] + public class ClassWithCustomConverter + { + public int MyInt { get; set; } + } + [JsonConverter(typeof(CustomConverter_StructWithCustomConverter))] public struct StructWithCustomConverter { @@ -223,16 +211,15 @@ public struct StructWithCustomConverter } [JsonConverter(typeof(CustomConverterFactory))] - public struct StructWithCustomConverterFactory + public class ClassWithCustomConverterFactory { public int MyInt { get; set; } } - [JsonConverter(typeof(JsonStringEnumConverter))] - public enum EnumWithJsonStringEnumConverter + [JsonConverter(typeof(CustomConverterFactory))] + public struct StructWithCustomConverterFactory { - A = 1, - B = 2 + public int MyInt { get; set; } } public class ClassWithCustomConverterProperty @@ -258,6 +245,30 @@ public struct StructWithCustomConverterProperty public ClassWithCustomConverterProperty.NestedPoco Property { get; set; } } + public struct ClassWithCustomConverterPropertyFactory + { + [JsonConverter(typeof(JsonStringEnumConverter))] // This converter is a JsonConverterFactory + public SampleEnum MyEnum { get; set; } + } + + public struct StructWithCustomConverterPropertyFactory + { + [JsonConverter(typeof(JsonStringEnumConverter))] // This converter is a JsonConverterFactory + public SampleEnum MyEnum { get; set; } + } + + public enum SampleEnum + { + A = 1, + B = 2 + } + + [JsonConverter(typeof(CustomConverter_StructWithCustomConverter))] // Invalid + public class ClassWithBadCustomConverter + { + public int MyInt { get; set; } + } + [JsonConverter(typeof(CustomConverter_StructWithCustomConverter))] // Invalid public struct StructWithBadCustomConverter { From d347a960d131ab5d5d68511eae9ab494f3e51440 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Fri, 3 Sep 2021 12:15:46 -0500 Subject: [PATCH 4/4] Ensure context helper methods properly generated for implicit context types --- .../gen/JsonSourceGenerator.Parser.cs | 29 ++++++---- .../MetadataContextTests.cs | 58 +++++++++++++++++++ 2 files changed, 76 insertions(+), 11 deletions(-) diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index 76c30b3c89bed..fecdb3792b8c1 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -823,6 +823,8 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener for (Type? currentType = type; currentType != null; currentType = currentType.BaseType) { + PropertyGenerationSpec spec; + foreach (PropertyInfo propertyInfo in currentType.GetProperties(bindingFlags)) { bool isVirtual = propertyInfo.IsVirtual(); @@ -833,11 +835,8 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener continue; } - PropertyGenerationSpec spec = GetPropertyGenerationSpec(propertyInfo, isVirtual, generationMode); - CacheMember(spec, ref propGenSpecList, ref ignoredMembers); - - propertyOrderSpecified |= spec.Order != 0; - hasPropertyFactoryConverters |= spec.HasFactoryConverter; + spec = GetPropertyGenerationSpec(propertyInfo, isVirtual, generationMode); + CacheMemberHelper(); } foreach (FieldInfo fieldInfo in currentType.GetFields(bindingFlags)) @@ -847,11 +846,19 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener continue; } - PropertyGenerationSpec spec = GetPropertyGenerationSpec(fieldInfo, isVirtual: false, generationMode); + spec = GetPropertyGenerationSpec(fieldInfo, isVirtual: false, generationMode); + CacheMemberHelper(); + } + + void CacheMemberHelper() + { CacheMember(spec, ref propGenSpecList, ref ignoredMembers); propertyOrderSpecified |= spec.Order != 0; hasPropertyFactoryConverters |= spec.HasFactoryConverter; + + // The property type may be implicitly in the context, so add that as well. + hasTypeFactoryConverter |= spec.TypeGenerationSpec.HasTypeFactoryConverter; } } @@ -875,10 +882,10 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener constructionStrategy, nullableUnderlyingTypeMetadata: nullableUnderlyingTypeGenSpec, converterInstatiationLogic, - implementsIJsonOnSerialized, - implementsIJsonOnSerializing, - hasTypeFactoryConverter, - hasPropertyFactoryConverters); + implementsIJsonOnSerialized : implementsIJsonOnSerialized, + implementsIJsonOnSerializing : implementsIJsonOnSerializing, + hasTypeFactoryConverter : hasTypeFactoryConverter, + hasPropertyFactoryConverters : hasPropertyFactoryConverters); return typeMetadata; } @@ -1177,7 +1184,7 @@ private static bool PropertyAccessorCanBeReferenced(MethodInfo? accessor) if (forType) { - return $"this.{Emitter.GetConverterFromFactoryMethodName}(typeof({type.GetCompilableName()}), new {converterType.GetCompilableName()}())"; + return $"{Emitter.GetConverterFromFactoryMethodName}(typeof({type.GetCompilableName()}), new {converterType.GetCompilableName()}())"; } else { 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 6e6d7b18159bc..945082d7e5d7b 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 @@ -116,6 +116,27 @@ internal partial class MetadataContext : JsonSerializerContext, ITestContext public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Metadata; } + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum EnumWrittenAsString + { + A = 1 + } + + [JsonSerializable(typeof(EnumWrittenAsString))] + public partial class ContextWithExplicitStringEnum : JsonSerializerContext + { + } + + public class PocoWithEnum + { + public EnumWrittenAsString MyEnum { get; set; } + } + + [JsonSerializable(typeof(PocoWithEnum))] + public partial class ContextWithImplicitStringEnum : JsonSerializerContext + { + } + public sealed class MetadataContextTests : RealWorldContextTests { public MetadataContextTests() : base(MetadataContext.Default, (options) => new MetadataContext(options)) { } @@ -155,5 +176,42 @@ public override void EnsureFastPathGeneratedAsExpected() Assert.Throws(() => MetadataContext.Default.ClassWithBadCustomConverter.Serialize); Assert.Throws(() => MetadataContext.Default.StructWithBadCustomConverter.Serialize); } + + [Fact] + public void EnsureHelperMethodGenerated_TypeFactory() + { + // There are 2 helper methods generated for obtaining a converter from a factory: + // - JsonConverter version that is property-based (that calls the one below) + // - JsonConverter version that is Type-based + // and this test verifies the latter one is generated. Other tests also have property-level + // factories and thus verify both are created. + + const string Json = "\"A\""; + + EnumWrittenAsString obj = EnumWrittenAsString.A; + + string json = JsonSerializer.Serialize(obj, ContextWithExplicitStringEnum.Default.EnumWrittenAsString); + Assert.Equal(Json, json); + + obj = JsonSerializer.Deserialize(Json, ContextWithExplicitStringEnum.Default.EnumWrittenAsString); + Assert.Equal(EnumWrittenAsString.A, obj); + } + + [Fact] + public void EnsureHelperMethodGenerated_ImplicitPropertyFactory() + { + // ContextWithImplicitStringEnum does not have an entry for EnumWrittenAsString since it is + // implictly added by PocoWithEnum. Verify helper methods are still being created properly. + + const string Json = "{\"MyEnum\":\"A\"}"; + + PocoWithEnum obj = new() { MyEnum = EnumWrittenAsString.A }; + + string json = JsonSerializer.Serialize(obj, ContextWithImplicitStringEnum.Default.PocoWithEnum); + Assert.Equal(Json, json); + + obj = JsonSerializer.Deserialize(Json, ContextWithImplicitStringEnum.Default.PocoWithEnum); + Assert.Equal(EnumWrittenAsString.A, obj.MyEnum); + } } }