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
Prev Previous commit
Next Next commit
Add source gen support + misc feedback
  • Loading branch information
steveharter committed Jul 8, 2021
commit a035411f58a41300ebf63224b8c834423260a634
11 changes: 11 additions & 0 deletions src/libraries/System.Text.Json/gen/JsonConstants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Text.Json.SourceGeneration
{
internal static class JsonConstants
{
public const string IJsonOnSerializedFullName = "System.Text.Json.Serialization.IJsonOnSerialized";
public const string IJsonOnSerializingFullName = "System.Text.Json.Serialization.IJsonOnSerializing";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,13 @@ private string GenerateForObject(TypeGenerationSpec typeMetadata)

if (typeMetadata.GenerateSerializationLogic)
{
serializeFuncSource = GenerateFastPathFuncForObject(typeCompilableName, serializeMethodName, typeMetadata.CanBeNull, properties);
serializeFuncSource = GenerateFastPathFuncForObject(
typeCompilableName,
serializeMethodName,
typeMetadata.CanBeNull,
typeMetadata.ImplementsIJsonOnSerialized,
typeMetadata.ImplementsIJsonOnSerializing,
properties);
serializeFuncNamedArg = $@"serializeFunc: {serializeMethodName}";
}
else
Expand Down Expand Up @@ -635,6 +641,8 @@ private string GenerateFastPathFuncForObject(
string typeInfoTypeRef,
string serializeMethodName,
bool canBeNull,
bool implementsIJsonOnSerialized,
bool implementsIJsonOnSerializing,
List<PropertyGenerationSpec>? properties)
{
JsonSourceGenerationOptionsAttribute options = _currentContext.GenerationOptions;
Expand All @@ -646,6 +654,12 @@ private string GenerateFastPathFuncForObject(
StringBuilder sb = new();

// Begin method definition
if (implementsIJsonOnSerializing)
{
sb.Append($@"(({JsonConstants.IJsonOnSerializingFullName}){ValueVarName}).OnSerializing();");
sb.Append($@"{Environment.NewLine} ");
}

sb.Append($@"{WriterVarName}.WriteStartObject();");

if (properties != null)
Expand Down Expand Up @@ -733,6 +747,12 @@ private string GenerateFastPathFuncForObject(

{WriterVarName}.WriteEndObject();");

if (implementsIJsonOnSerialized)
{
sb.Append($@"{Environment.NewLine} ");
sb.Append($@"(({JsonConstants.IJsonOnSerializedFullName}){ValueVarName}).OnSerialized();");
};

return GenerateFastPathFuncForType(serializeMethodName, typeInfoTypeRef, sb.ToString(), canBeNull);
}

Expand Down
12 changes: 11 additions & 1 deletion src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,9 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener
bool foundDesignTimeCustomConverter = false;
string? converterInstatiationLogic = null;

bool implementsIJsonOnSerialized = false;
bool implementsIJsonOnSerializing = false;

IList<CustomAttributeData> attributeDataList = CustomAttributeData.GetCustomAttributes(type);
foreach (CustomAttributeData attributeData in attributeDataList)
{
Expand Down Expand Up @@ -577,6 +580,11 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener
constructionStrategy = ObjectConstructionStrategy.ParameterlessConstructor;
}

// GetInterface() is currently not implemented, so we use GetInterfaces().
IEnumerable<string> interfaces = type.GetInterfaces().Select(interfaceType => interfaceType.FullName);
implementsIJsonOnSerialized = interfaces.FirstOrDefault(interfaceName => interfaceName == JsonConstants.IJsonOnSerializedFullName) != null;
implementsIJsonOnSerializing = interfaces.FirstOrDefault(interfaceName => interfaceName == JsonConstants.IJsonOnSerializingFullName) != null;

for (Type? currentType = type; currentType != null; currentType = currentType.BaseType)
{
const BindingFlags bindingFlags =
Expand Down Expand Up @@ -627,7 +635,9 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener
collectionValueTypeMetadata: collectionValueType != null ? GetOrAddTypeGenerationSpec(collectionValueType, generationMode) : null,
constructionStrategy,
nullableUnderlyingTypeMetadata: nullableUnderlyingType != null ? GetOrAddTypeGenerationSpec(nullableUnderlyingType, generationMode) : null,
converterInstatiationLogic);
converterInstatiationLogic,
implementsIJsonOnSerialized,
implementsIJsonOnSerializing);

return typeMetadata;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<CLSCompliant>false</CLSCompliant>
Expand Down Expand Up @@ -35,6 +35,7 @@
<Compile Include="..\Common\JsonSourceGenerationOptionsAttribute.cs" Link="Common\System\Text\Json\Serialization\JsonSourceGenerationOptionsAttribute.cs" />
<Compile Include="ClassType.cs" />
<Compile Include="CollectionType.cs" />
<Compile Include="JsonConstants.cs" />
<Compile Include="JsonSourceGenerator.cs" />
<Compile Include="JsonSourceGenerator.Emitter.cs" />
<Compile Include="JsonSourceGenerator.Parser.cs" />
Expand Down
9 changes: 8 additions & 1 deletion src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ internal class TypeGenerationSpec

public ClassType ClassType { get; private set; }

public bool ImplementsIJsonOnSerialized { get; private set; }
public bool ImplementsIJsonOnSerializing { get; private set; }

public bool IsValueType { get; private set; }

public bool CanBeNull { get; private set; }
Expand Down Expand Up @@ -67,7 +70,9 @@ public void Initialize(
TypeGenerationSpec? collectionValueTypeMetadata,
ObjectConstructionStrategy constructionStrategy,
TypeGenerationSpec? nullableUnderlyingTypeMetadata,
string? converterInstantiationLogic)
string? converterInstantiationLogic,
bool implementsIJsonOnSerialized,
bool implementsIJsonOnSerializing)
{
GenerationMode = generationMode;
TypeRef = $"global::{typeRef}";
Expand All @@ -84,6 +89,8 @@ public void Initialize(
ConstructionStrategy = constructionStrategy;
NullableUnderlyingTypeMetadata = nullableUnderlyingTypeMetadata;
ConverterInstantiationLogic = converterInstantiationLogic;
ImplementsIJsonOnSerialized = implementsIJsonOnSerialized;
ImplementsIJsonOnSerializing = implementsIJsonOnSerializing;
}

private bool FastPathIsSupported()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace System.Text.Json.Serialization
/// </summary>
/// <remarks>
/// This behavior is only supported on types representing JSON objects.
/// Types that have a custom converter or represent collections or values do not support this behavior.
/// Types that have a custom converter or represent either collections or primitive values do not support this behavior.
/// </remarks>
public interface IJsonOnDeserialized
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace System.Text.Json.Serialization
/// </summary>
/// <remarks>
/// This behavior is only supported on types representing JSON objects.
/// Types that have a custom converter or represent collections or values do not support this behavior.
/// Types that have a custom converter or represent either collections or primitive values do not support this behavior.
/// </remarks>
public interface IJsonOnDeserializing
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace System.Text.Json.Serialization
/// </summary>
/// <remarks>
/// This behavior is only supported on types representing JSON objects.
/// Types that have a custom converter or represent collections or values do not support this behavior.
/// Types that have a custom converter or represent either collections or primitive values do not support this behavior.
/// </remarks>
public interface IJsonOnSerialized
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace System.Text.Json.Serialization
/// </summary>
/// <remarks>
/// This behavior is only supported on types representing JSON objects.
/// Types that have a custom converter or represent collections or values do not support this behavior.
/// Types that have a custom converter or represent either collections or primitive values do not support this behavior.
/// </remarks>
public interface IJsonOnSerializing
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public interface ITestContext
public JsonTypeInfo<HighLowTemps> HighLowTemps { get; }
public JsonTypeInfo<MyType> MyType { get; }
public JsonTypeInfo<MyType2> MyType2 { get; }
public JsonTypeInfo<MyTypeWithCallbacks> MyTypeWithCallbacks { get; }
public JsonTypeInfo<MyIntermediateType> MyIntermediateType { get; }
public JsonTypeInfo<HighLowTempsImmutable> HighLowTempsImmutable { get; }
public JsonTypeInfo<RealWorldContextTests.MyNestedClass> MyNestedClass { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ namespace System.Text.Json.SourceGeneration.Tests
[JsonSerializable(typeof(HighLowTemps))]
[JsonSerializable(typeof(MyType))]
[JsonSerializable(typeof(MyType2))]
[JsonSerializable(typeof(MyTypeWithCallbacks))]
[JsonSerializable(typeof(MyIntermediateType))]
[JsonSerializable(typeof(HighLowTempsImmutable))]
[JsonSerializable(typeof(RealWorldContextTests.MyNestedClass))]
Expand Down Expand Up @@ -45,6 +46,7 @@ public override void EnsureFastPathGeneratedAsExpected()
Assert.NotNull(MetadataAndSerializationContext.Default.HighLowTemps.Serialize);
Assert.NotNull(MetadataAndSerializationContext.Default.MyType.Serialize);
Assert.NotNull(MetadataAndSerializationContext.Default.MyType2.Serialize);
Assert.NotNull(MetadataAndSerializationContext.Default.MyTypeWithCallbacks.Serialize);
Assert.NotNull(MetadataAndSerializationContext.Default.MyIntermediateType.Serialize);
Assert.NotNull(MetadataAndSerializationContext.Default.HighLowTempsImmutable.Serialize);
Assert.NotNull(MetadataAndSerializationContext.Default.MyNestedClass.Serialize);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ namespace System.Text.Json.SourceGeneration.Tests
[JsonSerializable(typeof(HighLowTemps), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(MyTypeWithCallbacks), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(RealWorldContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Metadata)]
Expand Down Expand Up @@ -93,6 +94,7 @@ public override void EnsureFastPathGeneratedAsExpected()
Assert.Null(MetadataContext.Default.HighLowTemps.Serialize);
Assert.Null(MetadataContext.Default.MyType.Serialize);
Assert.Null(MetadataContext.Default.MyType2.Serialize);
Assert.Null(MetadataContext.Default.MyTypeWithCallbacks.Serialize);
Assert.Null(MetadataContext.Default.MyIntermediateType.Serialize);
Assert.Null(MetadataContext.Default.HighLowTempsImmutable.Serialize);
Assert.Null(MetadataContext.Default.MyNestedClass.Serialize);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ namespace System.Text.Json.SourceGeneration.Tests
[JsonSerializable(typeof(HighLowTemps), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.Default)]
[JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(MyTypeWithCallbacks), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(RealWorldContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)]
Expand Down Expand Up @@ -43,6 +44,7 @@ public override void EnsureFastPathGeneratedAsExpected()
Assert.NotNull(MixedModeContext.Default.HighLowTemps.Serialize);
Assert.NotNull(MixedModeContext.Default.MyType.Serialize);
Assert.NotNull(MixedModeContext.Default.MyType2.Serialize);
Assert.NotNull(MixedModeContext.Default.MyTypeWithCallbacks.Serialize);
Assert.NotNull(MixedModeContext.Default.MyIntermediateType.Serialize);
Assert.Null(MixedModeContext.Default.HighLowTempsImmutable.Serialize);
Assert.NotNull(MixedModeContext.Default.MyNestedClass.Serialize);
Expand Down Expand Up @@ -165,5 +167,24 @@ public override void SerializeObjectArray_WithCustomOptions()
VerifyIndexViewModel(index, JsonSerializer.Deserialize(indexAsJsonElement.GetRawText(), metadataContext.IndexViewModel));
VerifyCampaignSummaryViewModel(campaignSummary, JsonSerializer.Deserialize(campaignSummeryAsJsonElement.GetRawText(), metadataContext.CampaignSummaryViewModel));
}

[Fact]
public void OnSerializeCallbacks_WithCustomOptions()
{
MyTypeWithCallbacks obj = new();
Assert.Null(obj.MyProperty);

ITestContext context = SerializationContextWithCamelCase.Default;
Assert.Same(JsonNamingPolicy.CamelCase, ((JsonSerializerContext)context).Options.PropertyNamingPolicy);

string json = JsonSerializer.Serialize(obj, context.MyTypeWithCallbacks);
Assert.Equal("{\"myProperty\":\"Before\"}", json);
Assert.Equal("After", obj.MyProperty);

context = new MetadataContext(new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
json = JsonSerializer.Serialize(obj, context.MyTypeWithCallbacks);
Assert.Equal("{\"myProperty\":\"Before\"}", json);
Assert.Equal("After", obj.MyProperty);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ internal partial class SerializationContext : JsonSerializerContext, ITestContex
[JsonSerializable(typeof(HighLowTemps), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(MyTypeWithCallbacks), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(RealWorldContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)]
Expand All @@ -60,6 +61,7 @@ internal partial class SerializationWithPerTypeAttributeContext : JsonSerializer
[JsonSerializable(typeof(HighLowTemps), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(MyTypeWithCallbacks), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(RealWorldContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)]
Expand Down Expand Up @@ -93,6 +95,7 @@ public override void EnsureFastPathGeneratedAsExpected()
Assert.NotNull(SerializationContext.Default.HighLowTemps.Serialize);
Assert.NotNull(SerializationContext.Default.MyType.Serialize);
Assert.NotNull(SerializationContext.Default.MyType2.Serialize);
Assert.NotNull(SerializationContext.Default.MyTypeWithCallbacks.Serialize);
Assert.NotNull(SerializationContext.Default.MyIntermediateType.Serialize);
Assert.NotNull(SerializationContext.Default.HighLowTempsImmutable.Serialize);
Assert.NotNull(SerializationContext.Default.MyNestedClass.Serialize);
Expand Down Expand Up @@ -307,6 +310,17 @@ public override void ParameterizedConstructor()

JsonTestHelper.AssertThrows_PropMetadataInit(() => JsonSerializer.Deserialize(json, DefaultContext.HighLowTempsImmutable), typeof(HighLowTempsImmutable));
}

[Fact]
public void OnSerializeCallbacks()
{
MyTypeWithCallbacks obj = new();
Assert.Null(obj.MyProperty);

string json = JsonSerializer.Serialize(obj, DefaultContext.MyTypeWithCallbacks);
Assert.Equal("{\"MyProperty\":\"Before\"}", json);
Assert.Equal("After", obj.MyProperty);
}
}

public sealed class SerializationWithPerTypeAttributeContextTests : SerializationContextTests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Text.Json.Serialization;

namespace System.Text.Json.SourceGeneration.Tests.RepeatedTypes
{
Expand Down Expand Up @@ -108,6 +109,14 @@ public class MyIntermediateType
public MyType Type = new();
}

public class MyTypeWithCallbacks : IJsonOnSerializing, IJsonOnSerialized
{
public string MyProperty { get; set; }

public void OnSerializing() => MyProperty = "Before";
void IJsonOnSerialized.OnSerialized() => MyProperty = "After";
}

public class JsonMessage
{
public string Message { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,6 @@ private class MyClassWithSmallConstructor :
{
public int MyInt { get; set; }

[JsonConstructor]
public MyClassWithSmallConstructor(int myInt)
{
MyInt = myInt;
Expand Down Expand Up @@ -229,7 +228,6 @@ private class MyClassWithLargeConstructor :
public int MyInt4 { get; set; }
public int MyInt5 { get; set; }

[JsonConstructor]
public MyClassWithLargeConstructor(int myInt1, int myInt2, int myInt3, int myInt4, int myInt5)
{
MyInt1 = myInt1;
Expand Down