Order of fields when serializing the derived class in JSON.NET
Asked Answered
G

5

40

Consider these two classes:

public Class Base {
    public string Id {get; set;}
    public string Name {get; set;}
    public string LastName {get; set;}
}

And the derived class:

public Class Derived : Base {
    public string Address {get; set;}
    public DateTime DateOfBirth {get; set;}
}

When serializing the Derived class using Json.Net:

Derived record = new Derived record(); {// Initialize here...}
JsonConvert.SerializeObject(record);

By default, the properties of the Derived class appear first:

{ 
  "address": "test", 
  "date_of_birth" : "10/10/10",
  "id" : 007,
  "name" : "test name",
  "last_name": "test last name"
 }

What I need:

{ 
  "id" : 007,
  "name" : "test name",
  "last_name": "test last name"
  "address": "test", 
  "date_of_birth" : "10/10/10",      
 }

Question

Is it possible to have the base class properties come first, when serializing the derived class (without using [JsonProperty(Order=)] for each property of both classes)?

Granulation answered 14/9, 2015 at 18:38 Comment(7)
Is it worth asking the question "why do you need the order to be different?"Dismember
@TimBarrass Just to be more organized when manual testing and debugging.Granulation
According to the JSON standard, a JSON object is an * unordered set of name/value pairs*. So my recommendation would be to not worry about this.Joann
For testing of JSON, I find JToken.DeepEquals to be very useful, it eliminates differences due purely to formatting.Joann
What can't you use the Order= attribute?Injunction
@MarcBernier It's so complicated; this is a shared model which is being used by other teams, they cannot use any attributes. Also our class has so many properties and we use multi-level inheritance, (so the array of json objects sometimes have many name/value pairs) sometimes it gets difficult to compare the results if it's not in order. (by testing I actually meant quickly checking and comparing some values of the records.)Granulation
I don't think the 'Order' numbers have to be sequential. Maybe you could allocate 'bands' for each child (ie, base is 1-10, child A is 11-20, child B is 21-30, etc).Injunction
J
31

According to the JSON standard, a JSON object is an unordered set of name/value pairs. So my recommendation would be to not worry about property order. Nevertheless you can get the order you want by creating your own ContractResolver inheriting from one of the standard contract resolvers, and then overriding CreateProperties:

public class BaseFirstContractResolver : DefaultContractResolver
{
    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization) =>
        base.CreateProperties(type, memberSerialization)
            ?.OrderBy(p => p.DeclaringType.BaseTypesAndSelf().Count()).ToList();
}

public static class TypeExtensions
{
    public static IEnumerable<Type> BaseTypesAndSelf(this Type type)
    {
        while (type != null)
        {
            yield return type;
            type = type.BaseType;
        }
    }
}

And then use it like:

// Cache an instance of the resolver for performance
static IContractResolver baseFirstResolver = new BaseFirstContractResolver { /* Set any required properties here e.g.  NamingStrategy = new CamelCaseNamingStrategy() */ };

// And use the cached instance when serializing and deserializing
var settings = new JsonSerializerSettings 
{ 
    ContractResolver = baseFirstResolver, 
    // Add your other settings here.
    TypeNameHandling = TypeNameHandling.Objects 
};
var json = JsonConvert.SerializeObject(derived, typeof(Base), Formatting.Indented, settings);

Notes:

  • This approach works especially well with multi-level type hierarchies as it automates correct ordering of properties from all levels in the hierarchy.

  • Newtonsoft recommends caching instances of contract resolvers for best performance.

Demo fiddle here.

Joann answered 14/9, 2015 at 19:48 Comment(1)
it probably makes sense to cache type level since your are going to calculate it for each property in the same classMummify
E
54

Just as a complement, another approach different than the accepted answer is using [JsonProperty(Order = -2)]; You can modify your base class as follow:

public class Base
{
    [JsonProperty(Order = -2)]
    public string Id { get; set; }

    [JsonProperty(Order = -2)]
    public string Name { get; set; }

    [JsonProperty(Order = -2)]
    public string LastName { get; set; }
}

The reason of setting Order values to -2 is that every property without an explicit Order value has a value of -1 by default. So you need to either give all child properties an Order value, or just set your base class' properties to -2.

Excoriation answered 15/8, 2017 at 9:29 Comment(5)
I think [JsonProperty(Order = int.MinValue)] is self explanatory (-2 appears to be an arbitrary value)Rod
Yes but in the question, I mentioned without using [JsonProperty(Order=)] for each property of both classes. The reason is I was working on a multiplatform project that the [JsonProperty] attribute was not available to use.Granulation
You're right. As I mentioned, it was just a complementary answer for those who may find this question merely by its title, which is more general. So both approaches can be find in one place.Excoriation
Alternately, my derived class added just one property to the base class. I found easier and cleaner to add [JsonProperty(Order = 1)] to that property in the derived classKimberliekimberlin
@JoseRamonGarcia actually, in this case, -2 is clearer because it implies a -3, -4, etc., if required. In other words, it defines a range. int.MinValue implies a special single value.Gallbladder
J
31

According to the JSON standard, a JSON object is an unordered set of name/value pairs. So my recommendation would be to not worry about property order. Nevertheless you can get the order you want by creating your own ContractResolver inheriting from one of the standard contract resolvers, and then overriding CreateProperties:

public class BaseFirstContractResolver : DefaultContractResolver
{
    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization) =>
        base.CreateProperties(type, memberSerialization)
            ?.OrderBy(p => p.DeclaringType.BaseTypesAndSelf().Count()).ToList();
}

public static class TypeExtensions
{
    public static IEnumerable<Type> BaseTypesAndSelf(this Type type)
    {
        while (type != null)
        {
            yield return type;
            type = type.BaseType;
        }
    }
}

And then use it like:

// Cache an instance of the resolver for performance
static IContractResolver baseFirstResolver = new BaseFirstContractResolver { /* Set any required properties here e.g.  NamingStrategy = new CamelCaseNamingStrategy() */ };

// And use the cached instance when serializing and deserializing
var settings = new JsonSerializerSettings 
{ 
    ContractResolver = baseFirstResolver, 
    // Add your other settings here.
    TypeNameHandling = TypeNameHandling.Objects 
};
var json = JsonConvert.SerializeObject(derived, typeof(Base), Formatting.Indented, settings);

Notes:

  • This approach works especially well with multi-level type hierarchies as it automates correct ordering of properties from all levels in the hierarchy.

  • Newtonsoft recommends caching instances of contract resolvers for best performance.

Demo fiddle here.

Joann answered 14/9, 2015 at 19:48 Comment(1)
it probably makes sense to cache type level since your are going to calculate it for each property in the same classMummify
E
3

If you're using ASP.NET Core, don't override important contract resolver settings provided by default. Following from @dbc's answer, you can do this:

class DataContractJsonResolver : DefaultContractResolver
{
    public DataContractJsonResolver()
    {
        NamingStrategy = new CamelCaseNamingStrategy();
    }

    protected override IList<JsonProperty> CreateProperties( Type type, MemberSerialization memberSerialization )
    {
        return base.CreateProperties( type, memberSerialization )
            .OrderBy( p => BaseTypesAndSelf( p.DeclaringType ).Count() ).ToList();

        IEnumerable<Type> BaseTypesAndSelf( Type t )
        {
            while ( t != null ) {
                yield return t;
                t = t.BaseType;
            }
        }
    }
}
Eloisaeloise answered 12/2, 2018 at 17:56 Comment(0)
L
0

I'd also consider taking 'dbc's answer, but with the replacement of the 'OrderBy()' expression, with the following (- that does not rely upon the base-class having more properties than the derived one):

                    .OrderBy(
                        p =>
                            p.DeclaringType == type
                                ? 2
                                : 1)
                    .ThenBy(
                        p =>
                            p.Order ?? -1)

And then you don't need the "TypeExtensions", but it is repeating the 'p.Order' ordering (that has already taken place within the base class); given time & thought, there might be a better/more efficient way to do this.

Lowestoft answered 20/11, 2020 at 14:12 Comment(3)
I'm not ordering by number of properties I am ordering by depth of the DeclaringType in the type hierarchy.Joann
But you've got a (final) call to '.Count()' in the 'OrderBy()' call.Lowestoft
DeclaringType.BaseTypesAndSelf().Count() counts the number of base types in the inheritance hierarchy of the declaring type. A smaller count means that the property was declared higher up in the inheritance hierarchy (i.e. is from a base type), while a larger count means the property is from a more derived type.Joann
T
0

You can get the ordered properties "as is" from the base classes first like so:

public List<PropertyInfo> GetOrderedProperties(Type type)
{
    // Only get the properties for the current type and not its base type
    List<PropertyInfo> results = (type as TypeInfo)
            .DeclaredProperties
            .Where(propertyInfo => propertyInfo.CanRead && propertyInfo.CanWrite)
            .ToList();

    // Modify the above to your liking, but this presumes you want to add
    // every property that can be read and written to.

    // Insert the base type properties before the current type's properties
    if (type.BaseType != null && type.BaseType != typeof(object))
    {
        // Recursive method call
        results.InsertRange(0, GetOrderedProperties(type.BaseType));
    }

    return results;
}

Then create a custom JsonConverter (Custom JsonConverter example here):

public class OrderedPropertiesConverter : JsonConverter
{
    //...
    public override void WriteJson(
        JsonWriter writer,
        object value,
        JsonSerializer serializer
    )
    {
        if (value is null) {
            writer.WriteNullValue();
            return;
        }

        writer.WriteStartObject();
        foreach (var property in GetOrderedProperties(typeof(value)))
        {
            writer.WritePropertyName(property.Name);
            writer.WriteValue(property.GetValue(value));
        }
        writer.WriteEndObject();
    }

    public List<PropertyInfo> GetOrderedProperties(Type type)
    {
        List<PropertyInfo> results = (type as TypeInfo)
                .DeclaredProperties
                .Where(propertyInfo =>
                    propertyInfo.CanRead
                    && propertyInfo.CanWrite)
                .ToList();

        if (type.BaseType != null && type.BaseType != typeof(object))
        {
            results.InsertRange(0, GetOrderedProperties(type.BaseType));
        }
    
        return results;
    }
    //...
}

Then use your custom OrderedPropertiesConverter converter:

var json = JsonConvert.SerializeObject(contacts,new OrderedPropertiesConverter());
Tip answered 7/11, 2023 at 6:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.