Skip to content

Commit

Permalink
Add property ordering feature (#55586)
Browse files Browse the repository at this point in the history
  • Loading branch information
steveharter authored Jul 13, 2021
1 parent f9076c7 commit 9720219
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 1 deletion.
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 @@ -832,6 +832,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 @@ -89,6 +89,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

0 comments on commit 9720219

Please sign in to comment.