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. The Person class has two constructors. I put the JsonConstructor attribute on one of the constructors:

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 a JSON string to the Person object:

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 7Code language: plaintext (plaintext)

This shows that it used the Person(int luckyNumber) constructor because that’s the one that has the JsonConstructor attribute. 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 it 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

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

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

This results in the following exception:

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

You have three options:

  • Update to .NET 5.
  • Write a custom JSON converter that creates the object using the parameterized constructor.
  • Use Newtonsoft instead.

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 7Code 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 C# conventions, method parameters use camel-casing (i.e. “luckyNumber”), 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 can capture unmatched JSON properties by using the JsonExtensionData attribute, but you can’t use this with a constructor parameter. So don’t do this:

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 13Code 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:

  • Load the JSON string with 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)

Tip: Use the nameof() operator instead of hardcoding property names.

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 137Code 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