C# – Deserialisieren Sie JSON mit einem bestimmten Konstruktor

C# – Deserialisieren Sie JSON mit einem bestimmten Konstruktor

Wenn Ihre Klasse über mehrere Konstruktoren verfügt, können Sie das JsonConstructor-Attribut verwenden, um anzugeben, welcher Konstruktor während der Deserialisierung verwendet werden soll. Hier ist ein Beispiel:

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)

Hinweis:JsonConstructor für System.Text.Json wurde in .NET 5 hinzugefügt.

Jetzt deserialisieren:

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)

Dies gibt aus:

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

Dies zeigt, dass der Konstruktor Person(int luckyNumber) verwendet wurde. Es hat die LuckyNumber übergeben JSON-Eigenschaft an den Konstruktor und legen Sie dann die verbleibenden Eigenschaften fest, die nicht an den Konstruktor übergeben wurden (nur Person.Name ).

Newtonsoft arbeitet mit Konstruktoren fast genauso wie System.Text.Json, was ich am Ende erklären werde.

Welchen Konstruktor wird System.Text.Json verwenden?

Beim Deserialisieren sucht System.Text.Json anhand der folgenden Vorrangregeln nach einem öffentlichen Konstruktor:

  • Verwenden Sie den öffentlichen Konstruktor mit dem JsonConstructor-Attribut.
[JsonConstructor]
public Person(int luckyNumber) //uses this one

public Person()
Code language: C# (cs)
  • Öffentlichen parameterlosen Konstruktor verwenden.
public Person(int luckyNumber)

public Person() //uses this one
Code language: C# (cs)
  • Verwenden Sie den einzigen öffentlichen Konstruktor.
public Person(int luckyNumber) //uses this one
Code language: C# (cs)

Beachten Sie, dass Sie das JsonConstructor-Attribut nicht hinzufügen müssen, wenn Sie nur einen einzigen parametrisierten Konstruktor haben. Ich würde jedoch vorschlagen, JsonConstructor zu verwenden, damit Sie in Zukunft nicht auf Überraschungen stoßen, wenn Sie jemals einen weiteren Konstruktor hinzufügen.

Fehler – Wenn kein geeigneter öffentlicher Konstruktor gefunden werden kann

Wenn kein geeigneter öffentlicher Konstruktor gefunden wird, erhalten Sie während der Deserialisierung die folgende Ausnahme:

Es wird nicht in der Fehlermeldung angegeben, aber Sie müssen eine öffentliche haben Konstrukteur. Hier sind ein paar Beispiele für Konstruktoren, die zu dieser Ausnahme führen würden.

  • Es gibt keinen öffentlichen Konstruktor.
internal Person() { }
Code language: C# (cs)
  • Es gibt mehrere parametrisierte Konstruktoren und JsonConstructor wird nicht verwendet.
public Person(int luckyNumber)

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

Fehler – JsonConstructor kann nicht mehrmals verwendet werden

Sie können das JsonConstructor-Attribut nur auf einen Konstruktor setzen, andernfalls erhalten Sie bei der Deserialisierung die folgende Ausnahme:

Hier ist ein Beispiel für die falsche mehrfache Verwendung von JsonConstructor:

[JsonConstructor]
public Person(int luckyNumber)

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

Hinweis:Dieses Problem tritt auch dann auf, wenn Sie JsonConstructor auf nicht-öffentliche Konstruktoren setzen (ja, obwohl System.Text.Json keine nicht-öffentlichen Konstruktoren verwendet) .

Vor .NET 5

Nehmen wir an, Sie haben nur einen parametrisierten Konstruktor:

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

Vor .NET 5 war für System.Text.Json ein parameterloser Konstruktor erforderlich. Wenn Sie also nur einen parametrisierten Konstruktor hätten, würde er die folgende Ausnahme auslösen:

Sie haben drei Möglichkeiten:

  • Update auf .NET 5.
  • Schreiben Sie einen benutzerdefinierten Konverter, der das Objekt mithilfe des parametrisierten Konstruktors erstellt.
  • Verwenden Sie stattdessen Newtonsoft.

Newtonsoft ist Ihre beste Option, wenn Sie nicht auf .NET 5 aktualisieren können und keinen benutzerdefinierten Konverter schreiben möchten. Hier ist ein Beispiel für die Verwendung von Newtonsoft:

using Newtonsoft.Json;

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

Dies gibt Folgendes aus und zeigt, dass es mit parametrisierten Konstruktoren umgeht:

Lucky number is 7Code language: plaintext (plaintext)

Konstruktorparameternamen

Wenn Sie einen parametrisierten Konstruktor verwenden, müssen Sie diese Regeln befolgen:

  • Der JSON-Eigenschaftsname muss mit einem Eigenschaftsnamen in der Klasse übereinstimmen (standardmäßig Groß-/Kleinschreibung beachten).
  • Der Name des Konstruktorparameters muss mit einem Eigenschaftsnamen in der Klasse übereinstimmen (standardmäßig ohne Berücksichtigung der Groß-/Kleinschreibung).

Wenn die Bedingungen für den Parameternamen nicht erfüllt sind, erhalten Sie eine Ausnahme:

Hier ist ein Beispiel für die Verwendung von Namen, die diese Bedingungen erfüllen. Nehmen wir an, Sie haben die folgende JSON:

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

Die Klasse benötigt eine Eigenschaft namens LuckyNumber . Per Konvention verwenden Parameter camelCasing, also fügen Sie einen Parameter namens luckyNumber hinzu :

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)

Es kann dies deserialisieren.

Fehler – Zuordnung zu einer Eigenschaft, die das JsonExtensionData-Attribut verwendet, nicht möglich

Ein weiterer Fehlertyp, auf den Sie beim Deserialisieren mit einem parametrisierten Konstruktor stoßen könnten, ist der folgende:

Sie verwenden das JsonExtensionData-Attribut, um JSON-Eigenschaften zu erfassen, bei denen es keine übereinstimmende Eigenschaft in der Klasse gibt. Sie können diese Eigenschaft nicht als Konstruktorparameter haben. Hier ist ein Beispiel:

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)

Sie müssen entweder das JsonExtensionData-Attribut aus der Eigenschaft entfernen oder diesen Parameter aus dem Konstruktor entfernen.

Wenn Sie das JsonConstructor-Attribut nicht verwenden können

Der Hauptgrund dafür, dass Sie das JsonConstructor-Attribut nicht verwenden können, liegt darin, dass Sie versuchen, eine Klasse zu deserialisieren, über die Sie keine Kontrolle haben und die Sie nicht ändern können. Sie haben zwei Möglichkeiten.

Option 1 – Unterklasse und Konstruktor hinzufügen

Angenommen, Sie verwenden die folgende Drittanbieterklasse, die Sie nicht ändern können, und Sie möchten den parametrisierten Konstruktor während der Deserialisierung verwenden:

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)

Sie können davon eine Unterklasse erstellen, einen Konstruktor hinzufügen und das JsonConstructor-Attribut verwenden (optional, wenn Sie nur einen Konstruktor haben):

using System.Text.Json.Serialization;

public class CustomPerson : Person
{
	[JsonConstructor]
	public CustomPerson(int luckyNumber) : base(luckyNumber) 
	{ }
}
Code language: C# (cs)

Deserialisieren Sie dann mit Ihrer Unterklasse:

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)

Es wird der parametrisierte Konstruktor verwendet. Dies gibt Folgendes aus:

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

Option 2 – Schreiben Sie einen benutzerdefinierten Konverter

Wenn Sie den Subclass-Ansatz nicht verwenden können (z. B. wenn Sie die Typen nicht im Voraus kennen oder es sich um eine versiegelte Klasse handelt), können Sie einen benutzerdefinierten Konverter schreiben, um den gewünschten Konstruktor zu verwenden.

Angenommen, Sie haben die folgende versiegelte Klasse und möchten den parametrisierten Konstruktor während der Deserialisierung verwenden:

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)

Fügen Sie einen benutzerdefinierten Konverter für die Person-Klasse hinzu. Implementieren Sie die Deserialisierung, indem Sie die folgenden Schritte in der Read()-Methode ausführen:

  • Parse JSON in ein JsonDocument.
  • Erhalten Sie die Eigenschaften, die zum Aufrufen des parametrisierten Konstruktors benötigt werden.
  • Erstellen Sie das Objekt mit dem parametrisierten Konstruktor.
  • Stellen Sie die restlichen Eigenschaften ein.
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)

Verwenden Sie nun den benutzerdefinierten Konverter während der Deserialisierung, indem Sie ihn zu JsonSerializerOptions.Converters:

hinzufügen
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)

Dies verwendet erfolgreich den benutzerdefinierten Konverter, der den parametrisierten Person(int luckyNumber)-Konstruktor wie gewünscht aufruft und Folgendes ausgibt:

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

Newtonsoft und Konstruktoren

Newtonsoft und System.Text.Json funktionieren meistens gleich, wenn es um Konstruktoren geht. Wenn Sie beispielsweise mehrere Konstruktoren haben, können Sie das JsonConstructor-Attribut verwenden, um anzugeben, welcher Konstruktor verwendet werden soll:

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)

Jetzt mit Newtonsoft deserialisieren:

using Newtonsoft.Json;

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

Dies gibt Folgendes aus und zeigt, dass der angegebene Konstruktor verwendet wurde:

Lucky number is 7

Deserialisieren mit einem nicht öffentlichen Konstruktor

System.Text.Json erfordert einen öffentlichen Konstruktor. Newtonsoft nicht. Es kann nicht-öffentliche Konstruktoren verwenden. Hier ist ein Beispiel:

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)

Hinweis:Um dies mit System.Text.Json zu tun, müssten Sie einen benutzerdefinierten Konverter schreiben und Reflektion verwenden, um den nicht öffentlichen Konstruktor zu finden.

Jetzt deserialisieren:

using Newtonsoft.Json;

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

Dies gibt Folgendes aus, was zeigt, dass es zu einem privaten Konstruktor deserialisieren kann:

Lucky number is 7