Updated on

To control property order in JSON output we set it explicitly: [JsonPropertyOrder] in System.Text.Json, and [JsonProperty(Order = …)] in Newtonsoft.Json. Converters and contract resolvers exist for the cases those two attributes cannot reach, such as sorting a whole type alphabetically, or ordering types we do not own.

Without an explicit order, both libraries write properties in reflection order, which puts a derived class’s own properties ahead of the ones it inherits. That default is observable, and it is not something to depend on.

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

Let’s start.

What Is JSON Property Order in C#?

JSON property order is the sequence in which an object’s properties appear in the serialized output. RFC 8259 defines a JSON object as an unordered collection of name/value pairs, so the order carries no meaning in the format itself: a parser reading {"Id":1,"Name":"Ana"} and one reading {"Name":"Ana","Id":1} see the same object.

It matters anyway, for reasons outside the format. An API contract may specify field order. A signature or a checksum computed over the serialized bytes changes when the order changes. A CSV or a spreadsheet generated from the JSON inherits the column order. A file that people read in a diff is easier to review when the fields do not move.

Both libraries let us set the order, and both offer the same two levels of control. We can annotate individual properties with an attribute, or we can replace the component that decides the order for an entire type.

Attributes handle the common case. The replaceable components handle the types we cannot annotate.

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, and for the related question of leaving properties out of the JSON entirely, we have a separate guide.

For this article, we are going to use a simple console application in .NET 10 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
);

In What Order Does .NET Serialize Properties by Default?

Neither library defines a default order of its own. Both ask reflection for the type’s properties and write them in the order they come back, which means the answer to “what is the default order” is really “what does Type.GetProperties() return”.

Through .NET 6 that order was documented as unspecified: not alphabetical, not declaration order, and not guaranteed to be stable between runs or between runtime versions. From .NET 7 onward it is deterministic, following the order the properties appear in the assembly’s metadata, which for a single class is the order we declared them in.

Inheritance is where the result surprises people. The derived class’s own properties come back first, then the base class’s, so a Student : Person serializes its own fields ahead of the ones it inherits.

Deterministic is not the same as guaranteed. Reordering members in the source file reorders the JSON, so anything that depends on the order gets an explicit one.

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 upon 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 it reflects the position of the member within the metadata of the assembly. The rule applies within a declaring type, not across the whole result: properties are returned one type at a time, derived type first, and ordered by MetadataToken inside each type. If we want a refresher on how reflection reads a type’s members, we have an article on that too.

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 RegistrationNumber { 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:

RegistrationNumber 385875977
Grade 385875978
Id 385875975
Name 385875976

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. The two Student properties carry the two highest tokens here and still come first, which is exactly why the token order only holds within a declaring type.

How Do We Order Properties With JsonPropertyOrder and JsonProperty?

System.Text.Json reads [JsonPropertyOrder(n)], and Newtonsoft.Json reads [JsonProperty(Order = n)]. Both take an integer, both sort ascending, and both leave properties that share a value in their default relative order.

The two libraries disagree about what an unmarked property is worth, and it is the one detail worth memorizing. In System.Text.Json an unmarked property counts as 0, so [JsonPropertyOrder(-1)] is enough to move a property to the front. In Newtonsoft.Json an unmarked property sorts as if it were -1, so the same job needs [JsonProperty(Order = -2)].

Inheritance follows the same rule, which makes the attribute the simplest fix for the derived-first default. Annotating Id on the base Vehicle class pulls it ahead of the derived Car properties, because an explicit order outranks reflection order entirely rather than competing with it.

Properties left unannotated keep falling back to reflection order among themselves.

If the question is the property names rather than their order, we cover renaming properties rather than reordering them and camelCase output across a whole project separately.

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"
}

How Do We Order Properties With a Custom 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. If we want the full picture of how a custom JsonConverter is built from scratch, we cover that separately.

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)
    {
        throw new NotSupportedException(
            "This converter only controls the order properties are written in. Deserialize without it.");
    }

    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();
    }
}

Read() throws deliberately. A converter that calls JsonSerializer.Deserialize<T>() with the same options instance it is registered in selects itself again on the way in, and that is infinite recursion rather than a handled error: the process dies with a stack overflow that no try block can catch. This converter exists to control the order properties are written in, so refusing to read is the honest behavior.

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 an Animal 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 Newtonsoft.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"
}

How Do We Order Properties With 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"
}

How Do We Order Properties With 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"
}

Which Ordering Approach Should We Use?

Start with the attribute. [JsonPropertyOrder] and [JsonProperty(Order = …)] cost one line, apply wherever the type is serialized, and are visible to the next person reading the class.

Reach for a converter when the rule is about the whole type rather than about individual properties: sorting every property alphabetically, for instance, where annotating each one would be noise that goes stale the moment somebody adds a field.

Reach for a resolver when the types are not ours to annotate. A contract resolver in Newtonsoft.Json and an IJsonTypeInfoResolver in System.Text.Json both sit in front of the serializer and reshape the contract for every type it handles, so a package’s models can be ordered without touching the package.

Scope is the other half of the choice. Attributes are permanent and travel with the type. Converters and resolvers attach to options, so the same class can serialize one way in one endpoint and another way elsewhere.

What we need to doSystem.Text.JsonNewtonsoft.Json
Move one property, class is ours to edit[JsonPropertyOrder(n)] on the property[JsonProperty(Order = n)] on the property
Put one property first[JsonPropertyOrder(-1)], because unmarked properties default to 0[JsonProperty(Order = -2)], because unmarked properties sort as -1
Sort a whole type, e.g. alphabeticallyCustom JsonConverter<T>Custom JsonConverter<T>
Sort every type, class not ours to editIJsonTypeInfoResolver: subclass DefaultJsonTypeInfoResolver, set JsonPropertyInfo.OrderIContractResolver: subclass DefaultContractResolver, override CreateProperties()
Apply it globallyJsonSerializerOptions.TypeInfoResolverJsonSerializerSettings.ContractResolver
Apply it to one call onlyAdd the converter to JsonSerializerOptions.ConvertersPass the converter to JsonConvert.SerializeObject()
Available since[JsonPropertyOrder] .NET 6; IJsonTypeInfoResolver .NET 7Both long-standing in Json.NET

If the answer is a resolver or a converter, it is usually worth setting these options once for the whole application rather than at every call site.

Which Ordering Approach Is Fastest?

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      |
|------------------------------------- |-------------:|------------:|------------:|
| PropertyOrderUsingSystemTextJson     |     695.6 ns |    10.85 ns |    12.06 ns |
| PropertyOrderUsingNewtonsoftJson     |     397.9 ns |     5.67 ns |     4.73 ns |
| PropertyConverterUsingSystemTextJson | 327,292.3 ns | 4,850.31 ns | 4,536.98 ns |
| PropertyConverterUsingNewtonsoftJson |     893.1 ns |    17.86 ns |    48.58 ns |
| TypeInfoResolverUsingSystemTextJson  |  23,383.6 ns |   286.74 ns |   268.22 ns |
| ContractResolverUsingNewtonsoftJson  | 520,592.4 ns | 5,148.33 ns | 4,563.86 ns |

Analyzing these results, the attribute path is by far the cheapest of the six for both libraries, and the two are close enough that neither wins it convincingly: Newtonsoft.Json comes in at 397.9 ns against 695.6 ns for System.Text.Json.

The converter and resolver rows are far larger, and the reason is the shape of the sample rather than the libraries. Every System.Text.Json call here builds a fresh JsonSerializerOptions, and a fresh options instance rebuilds the serializer’s metadata cache from scratch, so those rows measure setup much more than serialization. Reusing a single options instance for the converter path on the same machine takes it from roughly 780 µs per call to about 3.1 µs. The Newtonsoft.Json converter is passed straight to JsonConvert.SerializeObject with no options object to rebuild, which is why its row stays under a microsecond.

The one row-for-row comparison that survives that caveat is the resolver pair, where both libraries pay for a per-call object: IJsonTypeInfoResolver at 23.4 µs against DefaultContractResolver at 520.6 µs, roughly 22 times faster.

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 library can claim the best performance across the board, and our benchmark says less about the two of them than it does about how the options object is used: reuse one JsonSerializerOptions instance and System.Text.Json is quick, build a new one on every call and it is not. Each library does have minor differences in implementation, but both are equally capable, generally speaking.

Tested with .NET 10.0.10 and Newtonsoft.Json 13.0.4.