diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs
index 26b72dd6493aa..97e39311a09e7 100644
--- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs
+++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs
@@ -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
{
diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj
index 3206cbd6de1f6..08855731abe63 100644
--- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj
+++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj
@@ -22,7 +22,7 @@
-
+
@@ -89,6 +89,7 @@
+
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonPropertyOrderAttribute.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonPropertyOrderAttribute.cs
new file mode 100644
index 0000000000000..8126e75690f41
--- /dev/null
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonPropertyOrderAttribute.cs
@@ -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
+{
+ ///
+ /// 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.
+ ///
+ /// If multiple properties have the same value, the ordering is undefined between them.
+ [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
+ public sealed class JsonPropertyOrderAttribute : JsonAttribute
+ {
+ ///
+ /// Initializes a new instance of with the specified order.
+ ///
+ /// The order of the property.
+ public JsonPropertyOrderAttribute(int order)
+ {
+ Order = order;
+ }
+
+ ///
+ /// The serialization order of the property.
+ ///
+ public int Order { get; }
+ }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs
index 95081b8519dea..9e34cc60a3561 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs
@@ -74,6 +74,12 @@ internal virtual void GetPolicies(JsonIgnoreCondition? ignoreCondition, JsonNumb
DeterminePropertyName();
DetermineIgnoreCondition(ignoreCondition);
+ JsonPropertyOrderAttribute? orderAttr = GetAttribute(MemberInfo);
+ if (orderAttr != null)
+ {
+ Order = orderAttr.Order;
+ }
+
JsonNumberHandlingAttribute? attribute = GetAttribute(MemberInfo);
DetermineNumberHandlingForProperty(attribute?.Handling, declaringTypeNumberHandling);
}
@@ -366,6 +372,11 @@ internal abstract void InitializeForTypeInfo(
internal JsonSerializerOptions Options { get; set; } = null!; // initialized in Init method
+ ///
+ /// The property order.
+ ///
+ internal int Order { get; set; }
+
internal bool ReadJsonAndAddExtensionProperty(
object obj,
ref ReadStack state,
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs
index ba90400e24650..077de6d05372d 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs
@@ -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.
@@ -229,6 +231,7 @@ internal JsonTypeInfo(Type type, JsonConverter converter, Type runtimeType, Json
propertyInfo,
isVirtual,
typeNumberHandling,
+ ref propertyOrderSpecified,
ref ignoredMembers);
}
else
@@ -263,6 +266,7 @@ internal JsonTypeInfo(Type type, JsonConverter converter, Type runtimeType, Json
fieldInfo,
isVirtual: false,
typeNumberHandling,
+ ref propertyOrderSpecified,
ref ignoredMembers);
}
}
@@ -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!);
@@ -327,6 +336,7 @@ private void CacheMember(
MemberInfo memberInfo,
bool isVirtual,
JsonNumberHandling? typeNumberHandling,
+ ref bool propertyOrderSpecified,
ref Dictionary? ignoredMembers)
{
bool hasExtensionAttribute = memberInfo.GetCustomAttribute(typeof(JsonExtensionDataAttribute)) != null;
@@ -347,6 +357,7 @@ private void CacheMember(
else
{
CacheMember(jsonPropertyInfo, PropertyCache, ref ignoredMembers);
+ propertyOrderSpecified |= jsonPropertyInfo.Order != 0;
}
}
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PropertyOrderTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PropertyOrderTests.cs
new file mode 100644
index 0000000000000..07b16caa00dcd
--- /dev/null
+++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/PropertyOrderTests.cs
@@ -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(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(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(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
+ }
+ }
+}
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj
index 20d919ca164e2..9e117638ddb80 100644
--- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj
+++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj
@@ -166,6 +166,7 @@
+