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 feature #55586

Merged
merged 1 commit into from
Jul 13, 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
6 changes: 6 additions & 0 deletions src/libraries/System.Text.Json/ref/System.Text.Json.cs
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,12 @@ public sealed partial class JsonPropertyNameAttribute : System.Text.Json.Seriali
public JsonPropertyNameAttribute(string name) { }
public string Name { get { throw null; } }
}
[System.AttributeUsageAttribute(System.AttributeTargets.Field | System.AttributeTargets.Property, AllowMultiple = false)]
public sealed partial class JsonPropertyOrderAttribute : System.Text.Json.Serialization.JsonAttribute
{
public JsonPropertyOrderAttribute(int order) { }
public int Order { get { throw null; } }
}
[System.AttributeUsageAttribute(System.AttributeTargets.Class, AllowMultiple=true)]
public sealed partial class JsonSerializableAttribute : System.Text.Json.Serialization.JsonAttribute
{
Expand Down
3 changes: 2 additions & 1 deletion src/libraries/System.Text.Json/src/System.Text.Json.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<Compile Include="..\Common\JsonCamelCaseNamingPolicy.cs" Link="Common\System\Text\Json\JsonCamelCaseNamingPolicy.cs" />
<Compile Include="..\Common\JsonNamingPolicy.cs" Link="Common\System\Text\Json\JsonNamingPolicy.cs" />
<Compile Include="..\Common\JsonAttribute.cs" Link="Common\System\Text\Json\Serialization\JsonAttribute.cs" />
<Compile Include="..\Common\JsonHelpers.cs" Link ="Common\System\Text\Json\JsonHelpers.cs" />
<Compile Include="..\Common\JsonHelpers.cs" Link="Common\System\Text\Json\JsonHelpers.cs" />
<Compile Include="..\Common\JsonIgnoreCondition.cs" Link="Common\System\Text\Json\Serialization\JsonIgnoreCondition.cs" />
<Compile Include="..\Common\JsonKnownNamingPolicy.cs" Link="Common\System\Text\Json\Serialization\JsonKnownNamingPolicy.cs" />
<Compile Include="..\Common\JsonNumberHandling.cs" Link="Common\System\Text\Json\Serialization\JsonNumberHandling.cs" />
Expand Down Expand Up @@ -92,6 +92,7 @@
<Compile Include="System\Text\Json\Serialization\Attributes\JsonIncludeAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\Attributes\JsonNumberHandlingAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\Attributes\JsonPropertyNameAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\Attributes\JsonPropertyOrderAttribute.cs" />
<Compile Include="System\Text\Json\Serialization\Converters\JsonMetadataServicesConverter.cs" />
<Compile Include="System\Text\Json\Serialization\IgnoreReferenceResolver.cs" />
<Compile Include="System\Text\Json\Serialization\IJsonOnDeserialized.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// 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.Serialization
{
/// <summary>
/// Specifies the property order that is present in the JSON when serializing. Lower values are serialized first.
/// If the attribute is not specified, the default value is 0.
/// </summary>
/// <remarks>If multiple properties have the same value, the ordering is undefined between them.</remarks>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public sealed class JsonPropertyOrderAttribute : JsonAttribute
{
/// <summary>
/// Initializes a new instance of <see cref="JsonPropertyNameAttribute"/> with the specified order.
/// </summary>
/// <param name="order">The order of the property.</param>
public JsonPropertyOrderAttribute(int order)
{
Order = order;
}

/// <summary>
/// The serialization order of the property.
/// </summary>
public int Order { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ internal virtual void GetPolicies(JsonIgnoreCondition? ignoreCondition, JsonNumb
DeterminePropertyName();
DetermineIgnoreCondition(ignoreCondition);

JsonPropertyOrderAttribute? orderAttr = GetAttribute<JsonPropertyOrderAttribute>(MemberInfo);
if (orderAttr != null)
{
Order = orderAttr.Order;
}

JsonNumberHandlingAttribute? attribute = GetAttribute<JsonNumberHandlingAttribute>(MemberInfo);
DetermineNumberHandlingForProperty(attribute?.Handling, declaringTypeNumberHandling);
}
Expand Down Expand Up @@ -366,6 +372,11 @@ internal abstract void InitializeForTypeInfo(

internal JsonSerializerOptions Options { get; set; } = null!; // initialized in Init method

/// <summary>
/// The property order.
/// </summary>
internal int Order { get; set; }

internal bool ReadJsonAndAddExtensionProperty(
object obj,
ref ReadStack state,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@ internal JsonTypeInfo(Type type, JsonConverter converter, Type runtimeType, Json

PropertyInfo[] properties = type.GetProperties(bindingFlags);

bool propertyOrderSpecified = false;

// PropertyCache is not accessed by other threads until the current JsonTypeInfo instance
// is finished initializing and added to the cache on JsonSerializerOptions.
// Default 'capacity' to the common non-polymorphic + property case.
Expand Down Expand Up @@ -229,6 +231,7 @@ internal JsonTypeInfo(Type type, JsonConverter converter, Type runtimeType, Json
propertyInfo,
isVirtual,
typeNumberHandling,
ref propertyOrderSpecified,
ref ignoredMembers);
}
else
Expand Down Expand Up @@ -263,6 +266,7 @@ internal JsonTypeInfo(Type type, JsonConverter converter, Type runtimeType, Json
fieldInfo,
isVirtual: false,
typeNumberHandling,
ref propertyOrderSpecified,
ref ignoredMembers);
}
}
Expand All @@ -286,6 +290,11 @@ internal JsonTypeInfo(Type type, JsonConverter converter, Type runtimeType, Json
properties = currentType.GetProperties(bindingFlags);
};

if (propertyOrderSpecified)
{
PropertyCache.List.Sort((p1, p2) => p1.Value!.Order.CompareTo(p2.Value!.Order));
}

if (converter.ConstructorIsParameterized)
{
InitializeConstructorParameters(converter.ConstructorInfo!);
Expand Down Expand Up @@ -327,6 +336,7 @@ private void CacheMember(
MemberInfo memberInfo,
bool isVirtual,
JsonNumberHandling? typeNumberHandling,
ref bool propertyOrderSpecified,
ref Dictionary<string, JsonPropertyInfo>? ignoredMembers)
{
bool hasExtensionAttribute = memberInfo.GetCustomAttribute(typeof(JsonExtensionDataAttribute)) != null;
Expand All @@ -347,6 +357,7 @@ private void CacheMember(
else
{
CacheMember(jsonPropertyInfo, PropertyCache, ref ignoredMembers);
propertyOrderSpecified |= jsonPropertyInfo.Order != 0;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Xunit;

namespace System.Text.Json.Serialization.Tests
{
public static class PropertyOrderTests
{
private class MyPoco_BeforeAndAfter
{
public int B { get; set; }

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

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

[Fact]
public static void BeforeAndAfterDefaultOrder()
{
string json = JsonSerializer.Serialize<MyPoco_BeforeAndAfter>(new MyPoco_BeforeAndAfter());
Assert.Equal("{\"C\":0,\"B\":0,\"A\":0}", json);
}

private class MyPoco_After
{
[JsonPropertyOrder(2)]
public int C { get; set; }

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

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

[Fact]
public static void AfterDefaultOrder()
{
string json = JsonSerializer.Serialize<MyPoco_After>(new MyPoco_After());
Assert.EndsWith("\"A\":0,\"C\":0}", json);
// Order of B and D are not defined except they come before A and C
}

private class MyPoco_Before
{
[JsonPropertyOrder(-1)]
public int C { get; set; }

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

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

[Fact]
public static void BeforeDefaultOrder()
{
string json = JsonSerializer.Serialize<MyPoco_Before>(new MyPoco_Before());
Assert.StartsWith("{\"A\":0,\"C\":0", json);
// Order of B and D are not defined except they come after A and C
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@
<Compile Include="Serialization\OptionsTests.cs" />
<Compile Include="Serialization\PolymorphicTests.cs" />
<Compile Include="Serialization\PropertyNameTests.cs" />
<Compile Include="Serialization\PropertyOrderTests.cs" />
<Compile Include="Serialization\PropertyVisibilityTests.cs" />
<Compile Include="Serialization\ReadScenarioTests.cs" />
<Compile Include="Serialization\ReadValueTests.cs" />
Expand Down