Repository navigation
Support property ordering on POCOs #54748
Description
Background and Motivation
As part of making more extensible POCO converters and community asks, a low-risk feature for specifying ordering of properties\fields on a POCO for serialization is possible in the V6 timeframe. Other extensibility POCO features are no longer feasible for V6 since they need 1-2 preview releases to be properly vetted.
Today the serializer depends on reflection order for serializing object properties, and does base classes afterwards. Reflection is currently not deterministic and sometimes control over base class ordering is desired such as specifying base properties first.
The simplest way to implement this is through an attribute that specifies the order. However, this only works for owned POCOs. For V7+ once the JsonPropertyInfo is exposed publicly, that will allow for non-owned POCOs to also have this behavior.
Note that Newtonsoft has a similar approach with JsonPropertyAttribute.Order.
The source-generator should also leverage this attribute.
Proposed API
namespace System.Text.Json.Serialization
{
+ [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
+ public sealed class JsonPropertyOrderAttribute : JsonAttribute
+ {
+ public JsonPropertyNameAttribute(int order);
+ public int Order { get; }
+ }
}Usage Examples
public class Person
{
[JsonPropertyOrder(-1)] // Always appear first
public int Id { get; set; }
[JsonPropertyOrder(1)] // appear before LastName
public string FirstName { get; set; }
[JsonPropertyOrder(2)]
public string LastName{ get; set; }
public string City{ get; set; } // No order defined (has a order value of 0)
}If there is a "tie" in the values, the reflection order is used. This is also consistent with Newtsonsoft.
Alternative Designs
It is possible to add a "strategy" pattern here, like we do for property naming. However, as mentioned in the introduction, in V7+ allowing write access to the JsonPropertyInfo.Order property will likely be the most natural mechanism once JsonPropertyInfo is exposed publicly. This would be coupled with a mechanism to retrieve all properties, allowing, for example, properties to be sorted by base class first and\or sorted alphabetically without using [JsonPropertyOrder].
Activity
bugproof commentedon Jun 26, 2021
I think if someone wants to serialize base properties first it shouldn't be that tiresome as adding
[JsonPropertyOrder]attribute to every single property. I think users should be able to implement their own sorting strategies themselves or this behaviour should be a default one. E.g. to preserve the order it's possible to order byMetadataToken. Attributes are good but aren't very flexible.JsonSerializerOptionscould have some kind of sorting strategy that would be used hereruntime/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs
Line 120 in 650f28f
eiriktsarpalis commentedon Jun 28, 2021
Trying to understand the comment: Is
-1a special value that makes the field appear first or is it using the natural ordering of integers? (i.e. a member with[JsonPropertyOrder(-2)]would always appear before the above)zcsizmadia commentedon Jun 28, 2021
in 728 I suggested to use a similar property ordering what Newtonsoft.Json is using, where -1 is the default value for the order if attribute is not explicitly defined. I think what @steveharter is proposing a +1 what Newtonsoft.json is doing, so 0 is the default (instead of -1), -1 is to force to be first (instead of -2).
It might make sense to define a JsonPropertyFirst() attribute , instead of using a "weird" looking negative number
layomia commentedon Jun 29, 2021
Following offline discussion between @eiriktsarpalis and I, the proposal is to place no limitation on the integer values passed into the attribute ctor. If there are duplicates/ties, they'd be resolved by the serializer falling back to the reflection order (which would write props on derived classes first, before base). So props with order -3 would be written before -1, etc.
layomia commentedon Jun 29, 2021
We eventually want to allow this sort of imperative implementation, where STJ users can access the list of serializable properties and sort them themselves, using whatever sorting scheme they wish. This work isn't in scope for .NET 6.0, but is generally tracked by #34456.
zcsizmadia commentedon Jun 29, 2021
@layomia What do you think about a class Attribute to sort the props by name without individually setting the order value for each of them? props name sorting in this case should use the JsonPropertyNameAttribute for a prop if set.
terrajobst commentedon Jun 29, 2021
int.MaxValue / 2) so that people can force fields to be at the beginning or the end without having to apply the attribute to all propertiessvick commentedon Jun 29, 2021
@terrajobst
I'm not sure I understand. Are you saying that the example from the original post will be written like this?
To me, using -1, 1 and 2 (as originally proposed) is much clearer and it also doesn't require applying the attribute to all properties.
terrajobst commentedon Jun 29, 2021
The idea is: people generally don't care about the order of all the properties. For the subset they care, they might want to push them all the way to the top or all the way way to the end. Using
0as the default would mean that organizing the sort order of all fields at the beginning means the user has to use negative numbers, which isn't very intuitive. If we useint.MaxValueas the default, then pushing values to the end isn't very intuitive because the have to walk back fromint.MaxValue, which is akin to using negative numbers at the beginning.By using a number that is neither
0norint.MaxValuebut "high enough" the user can always assign values in ascending order:Keeping things last is a bit of a fringe scenario, so having to know the magic default value seems fine.
svick commentedon Jun 29, 2021
@terrajobst I think I understand now. With default 0, if you want to specify 1st, 2nd and 3rd item from the start, you would write them as -3, -2, -1 and that indeed isn't very intuitive, especially if that is the most common scenario.
The magic default value could be fixed by introducing a separate constant for it, but that's probably not worth it.
ladeak commentedon Jun 30, 2021
Here is a suggestion I have not heard being discussed during API review.
If a type has 5 properties and someone uses values 3 and -3 is a question, but should be able to settle in different ways.
steveharter commentedon Jul 1, 2021
For the comments on the ordering, the intent was to allow all integer values (including negative) and sort the properties by order (e.g. from
Int32.MinValuetoInt32.MaxValue). This is also what Newtonsoft does. It allows ordering of "before" and "after" the default value of 0.