In this article, we are going to learn how to perform property ordering within JSON serialization using the two most famous JSON libraries for .NET. The libraries are System.Text.Json, which has been natively available in the framework since .NET Core 3.0, and Newtonsoft.Json.

To download the source code for this article, you can visit our GitHub repository.

Let’s start.

What is JSON Property Ordering?

JSON property ordering is the arrangement of an object’s properties in JSON output. While the order of properties may not be significant in most cases, it can be crucial when working with APIs that require a specific order.

Fortunately, both System.Text.Json and Newtonsoft.Json provide the ability to specify a custom order for properties in JSON. To learn more about how to serialize objects, we can check out our article How to Turn a C# Object Into a JSON String in .NET.

For this article, we are going to use a simple console application in .NET 7 version and install the Newtonsoft.Json library through the NuGet Package Manager Console:

PM> Install-Package Newtonsoft.Json

To print formatted results, we are going to use the WriteIndented option from System.Text.Json and Formatting.Indented from the Newtonsoft.Json library:

JsonSerializer.Serialize(...,
    new JsonSerializerOptions { WriteIndented = true }
);

JsonConvert.SerializeObject(...,
    Formatting.Indented
);

Default Property Serialization Order

When serializing an object using System.Text.Json or Newtonsoft.Json, both libraries use reflection via the GetProperties method. In this case, the method first returns the properties of the derived class. If using inheritance, it will return the properties of the inherited classes in hierarchical order. It should be noted that from .NET 7 onward, the properties are returned in the order they appear in the assembly’s metadata:

In .NET 6 and earlier versions, the GetProperties method does not return properties in a particular order, such as alphabetical or declaration order. Your code must not depend on the order in which properties are returned, because that order varies. However, starting with .NET 7, the ordering is deterministic based on the metadata ordering in the assembly. Microsoft – System.Type.GetProperties

The MetadataToken is an integer value assigned to each member of a type by the runtime, and reflects the position of the member within the metadata of the assembly so that the properties with lower MetadataToken values are returned first. 

We can illustrate this behavior through an example. Let’s create a Person class and a Student derived class:

public class Person 
{ 
    public int Id { get; set; } 

    public string? Name { get; set; } 
}

public class Student: Person
{
    public int RegistratioNumber { get; set; }

    public double Grade { get; set; }
}

Now let’s get the properties through reflection using the GetProperties method:

var properties = typeof(Student)
    .GetProperties(BindingFlags.Public | BindingFlags.Instance)
    .ToList();

foreach (var property in properties)
{
    Console.WriteLine($"{property.Name} {property.MetadataToken}");
}

And, let’s check the output:

RegistratioNumber 385875971
Grade 385875972
Id 385875969
Name 385875970

As we can see, the MetadataToken values are generated according to the order of the properties of each class, first returning the properties of the derived class and proceeding upward through its inheritance hierarchy.

Order Using Property Attribute

To modify the default JSON serialization order of the System.Text.Json library, we can utilize the JsonPropertyOrder attribute on class properties that require a different order.

It’s worth noting that any property that lacks an order setting will receive a value of zero (0). Hence, if we wish to order a property first by using a positive value, we must assign a higher order to all other serialized properties. Alternatively, we can set the initial property’s order to -1.

Let’s create a Vehicle class and a Car derived class:

public class Vehicle
{
    [JsonPropertyOrder(-1)]
    public int Id { get; set; }

    public string? Manufacturer { get; set; }
}

public class Car: Vehicle
{
    public int NumberOfDoors { get; set; }
}

In our Program class, let’s serialize the Car object through the System.Text.Json library:

var car = new Car()
{
    NumberOfDoors = 4,
    Manufacturer = "Fiat",
    Id = 1
};

var json = JsonSerializer.Serialize(car,
    new JsonSerializerOptions { WriteIndented = true }
);

Console.WriteLine(json);

In the output, we have our object serialized, but with the Id property coming first:

{
  "Id": 1,
  "NumberOfDoors": 4,
  "Manufacturer": "Fiat"
}

To modify the serialization order via the Newtonsoft.Json library, we can utilize the [JsonProperty] attribute decorator from the Newtonsoft.Json namespace and specify the Order as -2. This is necessary because the default value, in this case, is -1:

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

    public string? Manufacturer { get; set; }
}

To serialize the object, let’s run the code:

var json = JsonConvert.SerializeObject(car,
    Formatting.Indented
);

Console.WriteLine(json);

As with the serialization with the previous library, our serialized object has had its default property ordering changed:

{
  "Id": 1,
  "NumberOfDoors": 4,
  "Manufacturer": "Fiat"
}

Order Using JsonConverter

In System.Text.Json, JsonConverter is a class that can be used to customize the serialization and deserialization of a type. A JsonConverter can be used to control how an object is converted to a JSON string and vice versa. 

Let’s create a JsonConverter that alphabetically sorts the properties of a class:

public class MicrosoftOrderedPropertiesConverter<T> : JsonConverter<T>
{
    public override bool CanConvert(Type typeToConvert)
    {
        return typeof(T).IsAssignableFrom(typeToConvert);
    }

    public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return JsonSerializer.Deserialize<T>(ref reader, options);
    }

    public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
    {
        writer.WriteStartObject();

        var properties = typeof(T).GetProperties().OrderBy(p => p.Name).ToList();

        foreach (var property in properties)
        {
            var propertyValue = property.GetValue(value);

            writer.WritePropertyName(property.Name);

            JsonSerializer.Serialize(writer, propertyValue, options);
        }

        writer.WriteEndObject();
    }
}

Through the Utf8JsonWriter, we start by calling WriteStartObject to start a new object. This method writes the opening brace character { to the output stream and sets the internal state of the writer to indicate that an object is being written.

After calling WriteStartObject, we use reflection to get the properties of an object at runtime by Type.GetProperties method. This method returns an array of PropertyInfo objects, each of which represents a property of the object, and then we use LINQ to order the properties by name.

Then, after ordering the properties, we write the object properties using WritePropertyName and use JsonSerializer.Serialize to output the property value.

After writing all the object properties, we call WriteEndObject to write the closing brace character } to the output stream and reset the internal state of the writer.

To demonstrate its use, let’s create anAnimal class and add the MicrosoftOrderedPropertiesConverter as an attribute class:

[JsonConverter(typeof(MicrosoftOrderedPropertiesConverter<Animal>))]
public class Animal
{
    public int Id { get; set; }

    public string? Name { get; set; }

    public int Age { get; set; }
}

And then we can serialize the Animal object:

var animal = new Animal()
{
    Id = 1,
    Name = "Miau",
    Age = 3
};

var json = JsonSerializer.Serialize(animal,
    new JsonSerializerOptions { WriteIndented = true }
);

Console.WriteLine(json);

The resulting JSON is:

{
  "Age": 3,
  "Id": 1,
  "Name": "Miau"
}

We can also register the converter through JsonSerializerOptions:

var options = new JsonSerializerOptions
{
    WriteIndented = true
};

options.Converters.Add(new MicrosoftOrderedPropertiesConverter<Animal>());

var json = JsonSerializer.Serialize(animal, options);

Console.WriteLine(json);

We can do the same in Newtosoft.Json, but with a few differences. Now, let’s show the same behavior using the JsonConverter from this library:

public class NewtonsoftOrderedPropertiesConverter<T> : JsonConverter<T>
{
    public override T? ReadJson(JsonReader reader, Type objectType, T? existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        if (!hasExistingValue)
        {
            existingValue = Activator.CreateInstance<T>();
        }

        serializer.Populate(reader, existingValue!);

        return existingValue;
    }

    public override void WriteJson(JsonWriter writer, T? value, JsonSerializer serializer)
    {
        writer.WriteStartObject();

        var properties = typeof(T).GetProperties().OrderBy(p => p.Name);

        foreach (var property in properties)
        {
            var propertyValue = property.GetValue(value);

            writer.WritePropertyName(property.Name);

            serializer.Serialize(writer, propertyValue);
        }

        writer.WriteEndObject();
    }
}

Now, we need to pass the custom NewtonsoftOrderedPropertiesConverter to the JsonSerializerSettings:

var json = JsonConvert.SerializeObject(animal,
    Formatting.Indented,
    new NewtonsoftOrderedPropertiesConverter<Animal>()
);

Console.WriteLine(json);

The result is the same:

{ 
    "Age": 3, 
    "Id": 1, 
    "Name": "Miau"
}

Order Using IContractResolver

By default, Newtonsoft.Json uses the DefaultContractResolver, which implements the IContractResolver interface and provides a set of default serialization contracts for the most common types. However, we can create our own custom contract resolver by implementing the IContractResolver interface. This approach enables us to define custom serialization contracts tailored to specific types or members.

For our case, we can sort the properties through the override of DefaultContractResolver:

public class OrderedPropertiesContractResolver : DefaultContractResolver
{
    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        var properties = base.CreateProperties(type, memberSerialization);

        return properties.OrderBy(p => p.PropertyName).ToList();
    }
}

And then, we can use it by instantiating it in the ContractResolver property of the JsonSerializerSettings class:

var json = JsonConvert.SerializeObject(animal,
    Formatting.Indented,
    new JsonSerializerSettings
    {
        ContractResolver = new OrderedPropertiesContractResolver()
    }
);

Console.WriteLine(json);

And the output is:

{ 
    "Age": 3, 
    "Id": 1, 
    "Name": "Miau" 
}

Order Using IJsonTypeInfoResolver

In previous versions, System.Text.Json only allowed us to make limited tweaks to the contract, just by annotating the System.Text.Json attribute.

Starting with .NET 7, users can write their own JSON contract resolution logic using implementations of the IJsonTypeInfoResolver interface. Contract resolution performed by the default serializer is exposed through the DefaultJsonTypeInfoResolver class, which implements IJsonTypeInfoResolver.

Let’s create a OrderedPropertiesJsonTypeInfoResolver class that overrides DefaultJsonTypeInfoResolver to sort the properties:

public class OrderedPropertiesJsonTypeInfoResolver: DefaultJsonTypeInfoResolver
{
    public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
    {
        var order = 0;

        JsonTypeInfo typeInfo = base.GetTypeInfo(type, options);

        if (typeInfo.Kind == JsonTypeInfoKind.Object)
        {
            foreach (JsonPropertyInfo property in typeInfo.Properties.OrderBy(a => a.Name))
            {
                property.Order = order++;
            }
        }

        return typeInfo;
    }
}

We override the GetTypeInfo method, which is called by the JsonSerializer to get information about a type being serialized or deserialized.

Inside the method, we first call the base implementation to get the default JsonTypeInfo object for the given type.

If it is an object, we get the collection of JsonPropertyInfo objects that provide information about each property in an object. Then, we sort by name using the OrderBy method and assign a unique order to each property by setting the Order property.

We can use this class by instantiating and setting it to the TypeInfoResolver property of the JsonSerializerOptions class:

var json = JsonSerializer.Serialize(instance,
    new JsonSerializerOptions 
    { 
        WriteIndented = true, 
        TypeInfoResolver = new OrderedPropertiesJsonTypeInfoResolver() 
    }
);

Console.WriteLine(json);

And the result is:

{ 
    "Age": 3, 
    "Id": 1, 
    "Name": "Miau" 
}

Performance

To make a quick comparison between the two libraries, let’s run a benchmark comparing the different property ordering methods that we describe in this article.

Let’s run the benchmark available in the repository of this article:

BenchmarkDotNet.Running.BenchmarkRunner.Run<OrderBenchmark>();

After running the benchmark we can inspect the result:

|                               Method |         Mean |       Error |       StdDev |       Median |
|------------------------------------- |-------------:|------------:|-------------:|-------------:|
|     PropertyOrderUsingSystemTextJson |     258.3 ns |     5.22 ns |     15.16 ns |     256.3 ns |
|     PropertyOrderUsingNewtonsoftJson |     520.2 ns |    14.48 ns |     42.70 ns |     505.4 ns |
| PropertyConverterUsingSystemTextJson |  11,583.2 ns |   226.40 ns |    378.26 ns |  11,583.0 ns |
| PropertyConverterUsingNewtonsoftJson |   1,278.6 ns |    25.47 ns |     53.73 ns |   1,278.6 ns |
|  TypeInfoResolverUsingSystemTextJson |  20,136.5 ns |   393.60 ns |    624.29 ns |  20,142.7 ns |
|  ContractResolverUsingNewtonsoftJson | 409,177.2 ns | 7,826.88 ns | 14,507.62 ns | 406,158.3 ns |

Analyzing these benchmark results, we may notice that we have a difference between the use of the ordering properties of the libraries, with System.Text.Json being faster than Newtonsoft.Json

On the other hand, when we look at the use of JsonConverter, we see a large advantage of Newtonsoft.Json over System.Text.Json, even though both have practically identical implementations.

Finally, we compare the conceptually similar type and contract resolvers. As we can see, using the resolver provided by System.Text.Json greatly outperforms its competitor.

Conclusion

Throughout this article, we’ve covered the property ordering process for the System.Text.Json and Newtonsoft.Json libraries. We’ve also described how to order properties individually, use converters, and implement a custom contract.

Neither of the libraries can claim the best performance across the board but System.Text.Json is better in more use cases according to our benchmarking. It’s important to note that each library does have minor differences in implementation, but both are equally capable, generally speaking.