Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Support JsonConverterFactory with src-gen
  • Loading branch information
steveharter authored and github-actions committed Sep 3, 2021
commit 386328587b078a29492b7942fed8cb8f4be237d6
68 changes: 32 additions & 36 deletions src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@ public interface ITestContext
public JsonTypeInfo<string> String { get; }
public JsonTypeInfo<RealWorldContextTests.ClassWithEnumAndNullable> ClassWithEnumAndNullable { get; }
public JsonTypeInfo<ClassWithCustomConverter> ClassWithCustomConverter { get; }
public JsonTypeInfo<ClassWithCustomConverterFactory> ClassWithCustomConverterFactory { get; }
public JsonTypeInfo<StructWithCustomConverter> StructWithCustomConverter { get; }
public JsonTypeInfo<ClassWithCustomConverterProperty> ClassWithCustomConverterProperty { get; }
public JsonTypeInfo<StructWithCustomConverterProperty> StructWithCustomConverterProperty { get; }
public JsonTypeInfo<StructWithCustomConverterFactory> StructWithCustomConverterFactory { get; }
public JsonTypeInfo<EnumWithJsonStringEnumConverter> EnumWithJsonStringEnumConverter { get; }
public JsonTypeInfo<ClassWithBadCustomConverter> ClassWithBadCustomConverter { get; }
public JsonTypeInfo<StructWithBadCustomConverter> StructWithBadCustomConverter { get; }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<InvalidOperationException>(() => MetadataAndSerializationContext.Default.ClassWithBadCustomConverter);
Assert.Throws<InvalidOperationException>(() => MetadataAndSerializationContext.Default.StructWithBadCustomConverter);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<InvalidOperationException>(() => MetadataWithPerTypeAttributeContext.Default.ClassWithBadCustomConverter.Serialize);
Assert.Throws<InvalidOperationException>(() => MetadataWithPerTypeAttributeContext.Default.StructWithBadCustomConverter.Serialize);
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<InvalidOperationException>(() => MetadataContext.Default.ClassWithBadCustomConverter.Serialize);
Assert.Throws<InvalidOperationException>(() => MetadataContext.Default.StructWithBadCustomConverter.Serialize);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<InvalidOperationException>(() => MixedModeContext.Default.ClassWithBadCustomConverter.Serialize);
Assert.Throws<InvalidOperationException>(() => MixedModeContext.Default.StructWithBadCustomConverter.Serialize);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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()
{
Expand Down
Loading