C# – Deserialize JSON with a specific constructor

When your class has multiple constructors, you can use the JsonConstructor attribute to specify which constructor to use during deserialization. Here’s an example:

using System.Text.Json.Serialization; public class Person { public string Name { get; set; } public int LuckyNumber { get; private set; } [JsonConstructor] public Person(int luckyNumber) { LuckyNumber = luckyNumber; } public Person() { } }
Code language: C# (cs)

Note: JsonConstructor for System.Text.Json was added in .NET 5.

Now deserialize:

using System.Text.Json; var person = JsonSerializer.Deserialize<Person>("{\"LuckyNumber\":7, \"Name\":\"Jason\"}"); Console.WriteLine($"{person.Name}'s lucky number is {person.LuckyNumber}");
Code language: C# (cs)

This outputs:

Jason's lucky number is 7
Code language: plaintext (plaintext)

This shows that it used the Person(int luckyNumber) constructor. It passed in the LuckyNumber JSON property to the constructor, and then set the remaining properties that weren’t passed into the constructor (just Person.Name).

Newtonsoft works with constructors almost the exactly the same way as System.Text.Json, which I’ll explain at the end.

Which constructor will System.Text.Json use?

When you deserialize, System.Text.Json looks for a public constructor using the following precedence rules:

  • Use public constructor with the JsonConstructor attribute.
[JsonConstructor] public Person(int luckyNumber) //uses this one public Person()
Code language: C# (cs)
  • Use public parameterless constructor.
public Person(int luckyNumber) public Person() //uses this one
Code language: C# (cs)
  • Use the only public constructor.
public Person(int luckyNumber) //uses this one
Code language: C# (cs)

Notice that you don’t need to add the JsonConstructor attribute if you only have a single parameterized constructor. However, I’d suggest using JsonConstructor just so you don’t run into surprises in the future if you ever add another constructor.

Error – When it can’t find a suitable public constructor

If a suitable public constructor isn’t found, you get the following exception during deserialization:

System.NotSupportedException: Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with ‘JsonConstructorAttribute’ is not supported

It’s not stated in the error message, but you’re required to have a public constructor. Here are a few examples of constructors that would result in this exception.

  • There’s no public constructor.
internal Person() { }
Code language: C# (cs)
  • There’s multiple parameterized constructors and JsonConstructor isn’t used.
public Person(int luckyNumber) public Person(int luckyNumber, string name)
Code language: C# (cs)

Error – Can’t use JsonConstructor multiple times

You can only put the JsonConstructor attribute on one constructor, otherwise you get the following exception during deserialization:

System.InvalidOperationException: The type ‘JsonConstructors.Person’ cannot have more than one member that has the attribute ‘System.Text.Json.Serialization.JsonConstructorAttribute’

Here’s an example of incorrectly using JsonConstructor multiple times:

[JsonConstructor] public Person(int luckyNumber) [JsonConstructor] public Person(int luckyNumber, string name)
Code language: C# (cs)

Note: This problem happens even if you put JsonConstructor on non-public constructors (yes, even though System.Text.Json won’t use non-public constructors).

Before .NET 5

Let’s say you only have a parameterized constructor:

public Person(int luckyNumber)
Code language: C# (cs)

Before .NET 5, System.Text.Json required a parameterless constructor. So if you only had a parameterized constructor, it’d throw the following exception:

System.NotSupportedException: Deserialization of reference types without parameterless constructor is not supported.

You have three options:

Newtonsoft is your best option if you can’t update to .NET 5 and don’t want to write a custom converter. Here’s an example of using Newtonsoft:

using Newtonsoft.Json; var person = JsonConvert.DeserializeObject<Person>("{\"LuckyNumber\":7}"); Console.WriteLine($"Lucky number is {person.LuckyNumber}");
Code language: C# (cs)

This outputs the following, showing that it handles parameterized constructors:

Lucky number is 7
Code language: plaintext (plaintext)

Constructor parameter names

When you’re using a parameterized constructor, you need to follow these rules:

  • The JSON property name must match a property name in the class (case-sensitive by default).
  • The constructor parameter name must match a property name in the class (case-insensitive by default).

If the parameter name conditions aren’t met, you get an exception:

System.InvalidOperationException: Each parameter in the deserialization constructor on type ‘JsonConstructors.Person’ must bind to an object property or field on deserialization. Each parameter name must match with a property or field on the object. The match can be case-insensitive.

Here’s an example of using names that meet these conditions. Let’s say you have the following JSON:

{ "LuckyNumber":7 }
Code language: JSON / JSON with Comments (json)

The class needs a property named LuckyNumber. By convention, parameters use camelCasing, so add a parameter named luckyNumber:

using System.Text.Json.Serialization; public class Person { public int LuckyNumber { get; private set; } [JsonConstructor] public Person(int luckyNumber) { LuckyNumber = luckyNumber; } public Person() { } }
Code language: C# (cs)

It’s able to deserialize this.

Error – Can’t map to a property that is using the JsonExtensionData attribute

Another type of error you might run into while deserializing with a parameterized constructor is the following:

System.InvalidOperationException: The extension data property ‘ExtensionData’ on type ‘JsonConstructors.Person’ cannot bind with a parameter in the deserialization constructor.

You use the JsonExtensionData attribute to capture JSON properties where there’s no matching property in the class. You can’t have this property as a constructor parameter. Here’s an example:

using System.Text.Json.Serialization; public class Person { [JsonExtensionData] public Dictionary<string, object> ExtensionData { get; set; } [JsonConstructor] public Person(Dictionary<string, object> ExtensionData) { } public Person() {} }
Code language: C# (cs)

You either have to remove the JsonExtensionData attribute from the property, or remove that parameter from the constructor.

When you can’t use the JsonConstructor attribute

The main reason for not being able to use the JsonConstructor attribute is because you’re trying to deserialize a class that you don’t have control over and can’t change. There are two options you can do.

Option 1 – Subclass and add a constructor

Let’s say you’re using the following third-party class that you can’t change, and you want to use the parameterized constructor during deserialization:

public class Person { public string Name { get; set; } public int LuckyNumber { get; private set; } public Person(int luckyNumber) { LuckyNumber = luckyNumber; } public Person() { } }
Code language: C# (cs)

You can subclass this, add a constructor, and use the JsonConstructor attribute (optional if you only have one constructor):

using System.Text.Json.Serialization; public class CustomPerson : Person { [JsonConstructor] public CustomPerson(int luckyNumber) : base(luckyNumber) { } }
Code language: C# (cs)

Then deserialize using your subclass:

using System.Text.Json; var person = JsonSerializer.Deserialize<CustomPerson>("{\"LuckyNumber\":13, \"Name\":\"Jason\"}"); Console.WriteLine($"{person.Name}'s lucky number is {person.LuckyNumber}");
Code language: C# (cs)

It will use the parameterized constructor. This outputs the following:

Jason's lucky number is 13
Code language: plaintext (plaintext)

Option 2 – Write a custom converter

If you can’t use the subclass approach (such as when you don’t know the types ahead of time, or you’re dealing with a sealed class), you can write a custom converter to use the constructor you want.

Let’s say you have the following sealed class and you want to use the parameterized constructor during deserialization:

public sealed class Person { public string Name { get; set; } public int LuckyNumber { get; private set; } public Person(int luckyNumber) { LuckyNumber = luckyNumber; } public Person() { } }
Code language: C# (cs)

Add a custom converter for the Person class. Implement deserialization by doing the following steps in the Read() method:

  • Parse JSON into a JsonDocument.
  • Get the properties needed to call the parameterized constructor.
  • Create the object with the parameterized constructor.
  • Set the rest of the properties.
using System.Text.Json; using System.Text.Json.Serialization; public class PersonConverter : JsonConverter<Person> { public override Person Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (!JsonDocument.TryParseValue(ref reader, out JsonDocument jsonDoc)) throw new JsonException("PersonConverter couldn't parse Person JSON"); var personJson = jsonDoc.RootElement; //Get properties for constructor var luckyNumber = personJson.GetProperty(nameof(Person.LuckyNumber)).GetInt32(); return new Person(luckyNumber) { //populate the remaining elements Name = personJson.GetProperty(nameof(Person.Name)).GetString() }; } public override void Write(Utf8JsonWriter writer, Person value, JsonSerializerOptions options) { var optsWithoutThisConverter = new JsonSerializerOptions(options); optsWithoutThisConverter.Converters.Remove(this); //prevent recursion JsonSerializer.Serialize<Person>(writer, value, optsWithoutThisConverter); } }
Code language: C# (cs)

Now use the custom converter during deserialization by adding it to JsonSerializerOptions.Converters:

using System.Text.Json; var options = new JsonSerializerOptions(); options.Converters.Add(new PersonConverter()); var person = JsonSerializer.Deserialize<Person>("{\"LuckyNumber\":137, \"Name\":\"Albert\"}", options); Console.WriteLine($"{person.Name}'s lucky number is {person.LuckyNumber}");
Code language: C# (cs)

This successfully uses the custom converter, which calls the parameterized Person(int luckyNumber) constructor as desired, and outputs the following:

Albert's lucky number is 137
Code language: plaintext (plaintext)

Newtonsoft and constructors

Newtonsoft and System.Text.Json mostly work the same when it comes to constructors. For example, when you have multiple constructors, you can use the JsonConstructor attribute to specify which constructor to use:

using Newtonsoft.Json; public class Person { public int LuckyNumber { get; private set; } [JsonConstructor] public Person(int luckyNumber) { LuckyNumber = luckyNumber; } public Person() { } }
Code language: C# (cs)

Now deserialize with Newtonsoft:

using Newtonsoft.Json; var person = JsonConvert.DeserializeObject<Person>("{\"LuckyNumber\":7}"); Console.WriteLine($"Lucky number is {person.LuckyNumber}");
Code language: C# (cs)

This outputs the following, showing that it used the specified constructor:

Lucky number is 7

Deserialize with a non-public constructor

System.Text.Json requires you to have a public constructor. Newtonsoft doesn’t. It can use non-public constructors. Here’s an example:

using Newtonsoft.Json; public class Person { public int LuckyNumber { get; private set; } [JsonConstructor] private Person(int luckyNumber) { LuckyNumber = luckyNumber; } public Person() { } }
Code language: C# (cs)

Note: To do this with System.Text.Json, you’d have to write a custom converter and use reflection to find the non-public constructor.

Now deserialize:

using Newtonsoft.Json; var person = JsonConvert.DeserializeObject<Person>("{\"LuckyNumber\":7}"); Console.WriteLine($"Lucky number is {person.LuckyNumber}");
Code language: C# (cs)

This outputs the following, showing that it is able to deserialize to a private constructor:

Lucky number is 7

Leave a Comment