Skip to content

Commit 9720219

Browse files
authored
Add property ordering feature (#55586)
1 parent f9076c7 commit 9720219

File tree

7 files changed

+127
-1
lines changed

7 files changed

+127
-1
lines changed

src/libraries/System.Text.Json/ref/System.Text.Json.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -832,6 +832,12 @@ public sealed partial class JsonPropertyNameAttribute : System.Text.Json.Seriali
832832
public JsonPropertyNameAttribute(string name) { }
833833
public string Name { get { throw null; } }
834834
}
835+
[System.AttributeUsageAttribute(System.AttributeTargets.Field | System.AttributeTargets.Property, AllowMultiple = false)]
836+
public sealed partial class JsonPropertyOrderAttribute : System.Text.Json.Serialization.JsonAttribute
837+
{
838+
public JsonPropertyOrderAttribute(int order) { }
839+
public int Order { get { throw null; } }
840+
}
835841
[System.AttributeUsageAttribute(System.AttributeTargets.Class, AllowMultiple=true)]
836842
public sealed partial class JsonSerializableAttribute : System.Text.Json.Serialization.JsonAttribute
837843
{

src/libraries/System.Text.Json/src/System.Text.Json.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
<Compile Include="..\Common\JsonCamelCaseNamingPolicy.cs" Link="Common\System\Text\Json\JsonCamelCaseNamingPolicy.cs" />
2323
<Compile Include="..\Common\JsonNamingPolicy.cs" Link="Common\System\Text\Json\JsonNamingPolicy.cs" />
2424
<Compile Include="..\Common\JsonAttribute.cs" Link="Common\System\Text\Json\Serialization\JsonAttribute.cs" />
25-
<Compile Include="..\Common\JsonHelpers.cs" Link ="Common\System\Text\Json\JsonHelpers.cs" />
25+
<Compile Include="..\Common\JsonHelpers.cs" Link="Common\System\Text\Json\JsonHelpers.cs" />
2626
<Compile Include="..\Common\JsonIgnoreCondition.cs" Link="Common\System\Text\Json\Serialization\JsonIgnoreCondition.cs" />
2727
<Compile Include="..\Common\JsonKnownNamingPolicy.cs" Link="Common\System\Text\Json\Serialization\JsonKnownNamingPolicy.cs" />
2828
<Compile Include="..\Common\JsonNumberHandling.cs" Link="Common\System\Text\Json\Serialization\JsonNumberHandling.cs" />
@@ -89,6 +89,7 @@
8989
<Compile Include="System\Text\Json\Serialization\Attributes\JsonIncludeAttribute.cs" />
9090
<Compile Include="System\Text\Json\Serialization\Attributes\JsonNumberHandlingAttribute.cs" />
9191
<Compile Include="System\Text\Json\Serialization\Attributes\JsonPropertyNameAttribute.cs" />
92+
<Compile Include="System\Text\Json\Serialization\Attributes\JsonPropertyOrderAttribute.cs" />
9293
<Compile Include="System\Text\Json\Serialization\Converters\JsonMetadataServicesConverter.cs" />
9394
<Compile Include="System\Text\Json\Serialization\IgnoreReferenceResolver.cs" />
9495
<Compile Include="System\Text\Json\Serialization\IJsonOnDeserialized.cs" />
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace System.Text.Json.Serialization
5+
{
6+
/// <summary>
7+
/// Specifies the property order that is present in the JSON when serializing. Lower values are serialized first.
8+
/// If the attribute is not specified, the default value is 0.
9+
/// </summary>
10+
/// <remarks>If multiple properties have the same value, the ordering is undefined between them.</remarks>
11+
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
12+
public sealed class JsonPropertyOrderAttribute : JsonAttribute
13+
{
14+
/// <summary>
15+
/// Initializes a new instance of <see cref="JsonPropertyNameAttribute"/> with the specified order.
16+
/// </summary>
17+
/// <param name="order">The order of the property.</param>
18+
public JsonPropertyOrderAttribute(int order)
19+
{
20+
Order = order;
21+
}
22+
23+
/// <summary>
24+
/// The serialization order of the property.
25+
/// </summary>
26+
public int Order { get; }
27+
}
28+
}

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ internal virtual void GetPolicies(JsonIgnoreCondition? ignoreCondition, JsonNumb
7474
DeterminePropertyName();
7575
DetermineIgnoreCondition(ignoreCondition);
7676

77+
JsonPropertyOrderAttribute? orderAttr = GetAttribute<JsonPropertyOrderAttribute>(MemberInfo);
78+
if (orderAttr != null)
79+
{
80+
Order = orderAttr.Order;
81+
}
82+
7783
JsonNumberHandlingAttribute? attribute = GetAttribute<JsonNumberHandlingAttribute>(MemberInfo);
7884
DetermineNumberHandlingForProperty(attribute?.Handling, declaringTypeNumberHandling);
7985
}
@@ -366,6 +372,11 @@ internal abstract void InitializeForTypeInfo(
366372

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

375+
/// <summary>
376+
/// The property order.
377+
/// </summary>
378+
internal int Order { get; set; }
379+
369380
internal bool ReadJsonAndAddExtensionProperty(
370381
object obj,
371382
ref ReadStack state,

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,8 @@ internal JsonTypeInfo(Type type, JsonConverter converter, Type runtimeType, Json
197197

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

200+
bool propertyOrderSpecified = false;
201+
200202
// PropertyCache is not accessed by other threads until the current JsonTypeInfo instance
201203
// is finished initializing and added to the cache on JsonSerializerOptions.
202204
// Default 'capacity' to the common non-polymorphic + property case.
@@ -229,6 +231,7 @@ internal JsonTypeInfo(Type type, JsonConverter converter, Type runtimeType, Json
229231
propertyInfo,
230232
isVirtual,
231233
typeNumberHandling,
234+
ref propertyOrderSpecified,
232235
ref ignoredMembers);
233236
}
234237
else
@@ -263,6 +266,7 @@ internal JsonTypeInfo(Type type, JsonConverter converter, Type runtimeType, Json
263266
fieldInfo,
264267
isVirtual: false,
265268
typeNumberHandling,
269+
ref propertyOrderSpecified,
266270
ref ignoredMembers);
267271
}
268272
}
@@ -286,6 +290,11 @@ internal JsonTypeInfo(Type type, JsonConverter converter, Type runtimeType, Json
286290
properties = currentType.GetProperties(bindingFlags);
287291
};
288292

293+
if (propertyOrderSpecified)
294+
{
295+
PropertyCache.List.Sort((p1, p2) => p1.Value!.Order.CompareTo(p2.Value!.Order));
296+
}
297+
289298
if (converter.ConstructorIsParameterized)
290299
{
291300
InitializeConstructorParameters(converter.ConstructorInfo!);
@@ -327,6 +336,7 @@ private void CacheMember(
327336
MemberInfo memberInfo,
328337
bool isVirtual,
329338
JsonNumberHandling? typeNumberHandling,
339+
ref bool propertyOrderSpecified,
330340
ref Dictionary<string, JsonPropertyInfo>? ignoredMembers)
331341
{
332342
bool hasExtensionAttribute = memberInfo.GetCustomAttribute(typeof(JsonExtensionDataAttribute)) != null;
@@ -347,6 +357,7 @@ private void CacheMember(
347357
else
348358
{
349359
CacheMember(jsonPropertyInfo, PropertyCache, ref ignoredMembers);
360+
propertyOrderSpecified |= jsonPropertyInfo.Order != 0;
350361
}
351362
}
352363

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Xunit;
5+
6+
namespace System.Text.Json.Serialization.Tests
7+
{
8+
public static class PropertyOrderTests
9+
{
10+
private class MyPoco_BeforeAndAfter
11+
{
12+
public int B { get; set; }
13+
14+
[JsonPropertyOrder(1)]
15+
public int A { get; set; }
16+
17+
[JsonPropertyOrder(-1)]
18+
public int C { get; set; }
19+
}
20+
21+
[Fact]
22+
public static void BeforeAndAfterDefaultOrder()
23+
{
24+
string json = JsonSerializer.Serialize<MyPoco_BeforeAndAfter>(new MyPoco_BeforeAndAfter());
25+
Assert.Equal("{\"C\":0,\"B\":0,\"A\":0}", json);
26+
}
27+
28+
private class MyPoco_After
29+
{
30+
[JsonPropertyOrder(2)]
31+
public int C { get; set; }
32+
33+
public int B { get; set; }
34+
public int D { get; set; }
35+
36+
[JsonPropertyOrder(1)]
37+
public int A { get; set; }
38+
}
39+
40+
[Fact]
41+
public static void AfterDefaultOrder()
42+
{
43+
string json = JsonSerializer.Serialize<MyPoco_After>(new MyPoco_After());
44+
Assert.EndsWith("\"A\":0,\"C\":0}", json);
45+
// Order of B and D are not defined except they come before A and C
46+
}
47+
48+
private class MyPoco_Before
49+
{
50+
[JsonPropertyOrder(-1)]
51+
public int C { get; set; }
52+
53+
public int B { get; set; }
54+
public int D { get; set; }
55+
56+
[JsonPropertyOrder(-2)]
57+
public int A { get; set; }
58+
}
59+
60+
[Fact]
61+
public static void BeforeDefaultOrder()
62+
{
63+
string json = JsonSerializer.Serialize<MyPoco_Before>(new MyPoco_Before());
64+
Assert.StartsWith("{\"A\":0,\"C\":0", json);
65+
// Order of B and D are not defined except they come after A and C
66+
}
67+
}
68+
}

src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@
166166
<Compile Include="Serialization\OptionsTests.cs" />
167167
<Compile Include="Serialization\PolymorphicTests.cs" />
168168
<Compile Include="Serialization\PropertyNameTests.cs" />
169+
<Compile Include="Serialization\PropertyOrderTests.cs" />
169170
<Compile Include="Serialization\PropertyVisibilityTests.cs" />
170171
<Compile Include="Serialization\ReadScenarioTests.cs" />
171172
<Compile Include="Serialization\ReadValueTests.cs" />

0 commit comments

Comments
 (0)