Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add property ordering to source gen #55662

Merged
merged 1 commit into from
Jul 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
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
19 changes: 19 additions & 0 deletions src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ private sealed class Parser

private const string JsonPropertyNameAttributeFullName = "System.Text.Json.Serialization.JsonPropertyNameAttribute";

private const string JsonPropertyOrderAttributeFullName = "System.Text.Json.Serialization.JsonPropertyOrderAttribute";

private readonly GeneratorExecutionContext _executionContext;

private readonly MetadataLoadContextInternal _metadataLoadContext;
Expand Down Expand Up @@ -606,6 +608,8 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener
BindingFlags.NonPublic |
BindingFlags.DeclaredOnly;

bool propertyOrderSpecified = false;

for (Type? currentType = type; currentType != null; currentType = currentType.BaseType)
{
foreach (PropertyInfo propertyInfo in currentType.GetProperties(bindingFlags))
Expand All @@ -620,6 +624,7 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener

PropertyGenerationSpec spec = GetPropertyGenerationSpec(propertyInfo, isVirtual, generationMode);
CacheMember(spec, ref propGenSpecList, ref ignoredMembers);
propertyOrderSpecified |= spec.Order != 0;
}

foreach (FieldInfo fieldInfo in currentType.GetFields(bindingFlags))
Expand All @@ -631,8 +636,14 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener

PropertyGenerationSpec spec = GetPropertyGenerationSpec(fieldInfo, isVirtual: false, generationMode);
CacheMember(spec, ref propGenSpecList, ref ignoredMembers);
propertyOrderSpecified |= spec.Order != 0;
}
}

if (propertyOrderSpecified)
{
propGenSpecList.Sort((p1, p2) => p1.Order.CompareTo(p2.Order));
}
}

typeMetadata.Initialize(
Expand Down Expand Up @@ -697,6 +708,7 @@ private PropertyGenerationSpec GetPropertyGenerationSpec(MemberInfo memberInfo,

bool foundDesignTimeCustomConverter = false;
string? converterInstantiationLogic = null;
int order = 0;

foreach (CustomAttributeData attributeData in attributeDataList)
{
Expand Down Expand Up @@ -745,6 +757,12 @@ private PropertyGenerationSpec GetPropertyGenerationSpec(MemberInfo memberInfo,
// Null check here is done at runtime within JsonSerializer.
}
break;
case JsonPropertyOrderAttributeFullName:
{
IList<CustomAttributeTypedArgument> ctorArgs = attributeData.ConstructorArguments;
order = (int)ctorArgs[0].Value;
}
break;
default:
break;
}
Expand Down Expand Up @@ -839,6 +857,7 @@ private PropertyGenerationSpec GetPropertyGenerationSpec(MemberInfo memberInfo,
SetterIsVirtual = setterIsVirtual,
DefaultIgnoreCondition = ignoreCondition,
NumberHandling = numberHandling,
Order = order,
HasJsonInclude = hasJsonInclude,
TypeGenerationSpec = GetOrAddTypeGenerationSpec(memberCLRType, generationMode),
DeclaringTypeRef = $"global::{memberInfo.DeclaringType.GetUniqueCompilableTypeName()}",
Expand Down
5 changes: 5 additions & 0 deletions src/libraries/System.Text.Json/gen/PropertyGenerationSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ internal sealed class PropertyGenerationSpec
/// </summary>
public JsonNumberHandling? NumberHandling { get; init; }

/// <summary>
/// The serialization order of the property.
/// </summary>
public int Order { get; init; }

/// <summary>
/// Whether the property has the JsonIncludeAttribute. If so, non-public accessors can be used for (de)serialziation.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public interface ITestContext
public JsonTypeInfo<MyType> MyType { get; }
public JsonTypeInfo<MyType2> MyType2 { get; }
public JsonTypeInfo<MyTypeWithCallbacks> MyTypeWithCallbacks { get; }
public JsonTypeInfo<MyTypeWithPropertyOrdering> MyTypeWithPropertyOrdering { 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 @@ -18,6 +18,7 @@ namespace System.Text.Json.SourceGeneration.Tests
[JsonSerializable(typeof(MyType))]
[JsonSerializable(typeof(MyType2))]
[JsonSerializable(typeof(MyTypeWithCallbacks))]
[JsonSerializable(typeof(MyTypeWithPropertyOrdering))]
[JsonSerializable(typeof(MyIntermediateType))]
[JsonSerializable(typeof(HighLowTempsImmutable))]
[JsonSerializable(typeof(RealWorldContextTests.MyNestedClass))]
Expand Down Expand Up @@ -47,6 +48,7 @@ public override void EnsureFastPathGeneratedAsExpected()
Assert.NotNull(MetadataAndSerializationContext.Default.MyType.Serialize);
Assert.NotNull(MetadataAndSerializationContext.Default.MyType2.Serialize);
Assert.NotNull(MetadataAndSerializationContext.Default.MyTypeWithCallbacks.Serialize);
Assert.NotNull(MetadataAndSerializationContext.Default.MyTypeWithPropertyOrdering.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 @@ -17,6 +17,7 @@ namespace System.Text.Json.SourceGeneration.Tests
[JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(MyTypeWithCallbacks), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(MyTypeWithPropertyOrdering), 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 @@ -67,6 +68,7 @@ public override void EnsureFastPathGeneratedAsExpected()
[JsonSerializable(typeof(MyType))]
[JsonSerializable(typeof(MyType2))]
[JsonSerializable(typeof(MyTypeWithCallbacks))]
[JsonSerializable(typeof(MyTypeWithPropertyOrdering))]
[JsonSerializable(typeof(MyIntermediateType))]
[JsonSerializable(typeof(HighLowTempsImmutable))]
[JsonSerializable(typeof(RealWorldContextTests.MyNestedClass))]
Expand Down Expand Up @@ -96,6 +98,7 @@ public override void EnsureFastPathGeneratedAsExpected()
Assert.Null(MetadataContext.Default.MyType.Serialize);
Assert.Null(MetadataContext.Default.MyType2.Serialize);
Assert.Null(MetadataContext.Default.MyTypeWithCallbacks.Serialize);
Assert.Null(MetadataContext.Default.MyTypeWithPropertyOrdering.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 @@ -17,6 +17,7 @@ namespace System.Text.Json.SourceGeneration.Tests
[JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.Default)]
[JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(MyTypeWithCallbacks), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(MyTypeWithPropertyOrdering), 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 @@ -45,6 +46,7 @@ public override void EnsureFastPathGeneratedAsExpected()
Assert.NotNull(MixedModeContext.Default.MyType.Serialize);
Assert.NotNull(MixedModeContext.Default.MyType2.Serialize);
Assert.NotNull(MixedModeContext.Default.MyTypeWithCallbacks.Serialize);
Assert.NotNull(MixedModeContext.Default.MyTypeWithPropertyOrdering.Serialize);
Assert.NotNull(MixedModeContext.Default.MyIntermediateType.Serialize);
Assert.Null(MixedModeContext.Default.HighLowTempsImmutable.Serialize);
Assert.NotNull(MixedModeContext.Default.MyNestedClass.Serialize);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -561,5 +561,13 @@ protected static void AssertFastPathLogicCorrect<T>(string expectedJson, T value

JsonTestHelper.AssertJsonEqual(expectedJson, Encoding.UTF8.GetString(ms.ToArray()));
}

[Fact]
public void PropertyOrdering()
{
MyTypeWithPropertyOrdering obj = new();
string json = JsonSerializer.Serialize(obj, DefaultContext.MyTypeWithPropertyOrdering);
Assert.Equal("{\"C\":0,\"B\":0,\"A\":0}", json);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ namespace System.Text.Json.SourceGeneration.Tests
[JsonSerializable(typeof(MyType))]
[JsonSerializable(typeof(MyType2))]
[JsonSerializable(typeof(MyTypeWithCallbacks))]
[JsonSerializable(typeof(MyTypeWithPropertyOrdering))]
[JsonSerializable(typeof(MyIntermediateType))]
[JsonSerializable(typeof(HighLowTempsImmutable))]
[JsonSerializable(typeof(RealWorldContextTests.MyNestedClass))]
Expand All @@ -40,6 +41,7 @@ internal partial class SerializationContext : JsonSerializerContext, ITestContex
[JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(MyTypeWithCallbacks), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(MyTypeWithPropertyOrdering), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(MyIntermediateType), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(HighLowTempsImmutable), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(RealWorldContextTests.MyNestedClass), GenerationMode = JsonSourceGenerationMode.Serialization)]
Expand All @@ -63,6 +65,7 @@ internal partial class SerializationWithPerTypeAttributeContext : JsonSerializer
[JsonSerializable(typeof(MyType), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(MyType2), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(MyTypeWithCallbacks), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(MyTypeWithPropertyOrdering), 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 @@ -97,6 +100,7 @@ public override void EnsureFastPathGeneratedAsExpected()
Assert.NotNull(SerializationContext.Default.MyType.Serialize);
Assert.NotNull(SerializationContext.Default.MyType2.Serialize);
Assert.NotNull(SerializationContext.Default.MyTypeWithCallbacks.Serialize);
Assert.NotNull(SerializationContext.Default.MyTypeWithPropertyOrdering.Serialize);
Assert.NotNull(SerializationContext.Default.MyIntermediateType.Serialize);
Assert.NotNull(SerializationContext.Default.HighLowTempsImmutable.Serialize);
Assert.NotNull(SerializationContext.Default.MyNestedClass.Serialize);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,18 @@ public class MyTypeWithCallbacks : IJsonOnSerializing, IJsonOnSerialized
void IJsonOnSerialized.OnSerialized() => MyProperty = "After";
}

public class MyTypeWithPropertyOrdering
{
public int B { get; set; }

[JsonPropertyOrder(1)]
public int A { get; set; }

[JsonPropertyOrder(-1)]
[JsonInclude]
public int C = 0;
}

public class JsonMessage
{
public string Message { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ private class MyPoco_BeforeAndAfter
public int A { get; set; }

[JsonPropertyOrder(-1)]
public int C { get; set; }
[JsonInclude]
public int C = 0;
}

[Fact]
Expand All @@ -28,7 +29,8 @@ public static void BeforeAndAfterDefaultOrder()
private class MyPoco_After
{
[JsonPropertyOrder(2)]
public int C { get; set; }
[JsonInclude]
public int C = 0;

public int B { get; set; }
public int D { get; set; }
Expand All @@ -48,7 +50,8 @@ public static void AfterDefaultOrder()
private class MyPoco_Before
{
[JsonPropertyOrder(-1)]
public int C { get; set; }
[JsonInclude]
public int C = 0;

public int B { get; set; }
public int D { get; set; }
Expand Down