Schreiben von benutzerdefinierten Konvertern für die JSON-Serialisierung (Marshallen) in .NETHow to write custom converters for JSON serialization (marshalling) in .NET

In diesem Artikel wird gezeigt, wie Sie benutzerdefinierte Konverter für JSON-Serialisierungsklassen erstellen, die im System.Text.Json-Namespace bereitgestellt werden.This article shows how to create custom converters for the JSON serialization classes that are provided in the System.Text.Json namespace. Eine Einführung zu System.Text.Json finden Sie unter Serialisieren und Deserialisieren von JSON-Daten in .NET.For an introduction to System.Text.Json, see How to serialize and deserialize JSON in .NET.

Ein Konverter ist eine Klasse, die ein Objekt oder einen Wert in und aus JSON konvertiert.A converter is a class that converts an object or a value to and from JSON. Der System.Text.Json-Namespace verfügt über integrierte Konverter für die meisten primitiven Typen, die JavaScript-Primitiven entsprechen.The System.Text.Json namespace has built-in converters for most primitive types that map to JavaScript primitives. Sie können benutzerdefinierte Konverter schreiben:You can write custom converters:

  • Um das Standardverhalten eines integrierten Konverters außer Kraft zu setzen.To override the default behavior of a built-in converter. Vielleicht möchten Sie zum Beispiel DateTime-Werte im Format „mm/dd/yyyy“ anstatt im Standardformat ISO 8601-1:2019 darstellen.For example, you might want DateTime values to be represented by mm/dd/yyyy format instead of the default ISO 8601-1:2019 format.
  • Um einen benutzerdefinierten Werttyp zu unterstützen.To support a custom value type. Beispielsweise eine PhoneNumber-Struktur.For example, a PhoneNumber struct.

Sie können auch benutzerdefinierte Konverter schreiben, um System.Text.Json mit Funktionen anzupassen oder zu erweitern, die nicht im aktuellen Release enthalten sind.You can also write custom converters to customize or extend System.Text.Json with functionality not included in the current release. Folgende Szenarien werden später in diesem Artikel abgedeckt:The following scenarios are covered later in this article:

Benutzerdefinierte KonvertermusterCustom converter patterns

Es gibt zwei Muster zum Erstellen eines benutzerdefinierten Konverters: das grundlegende Muster und das Factorymuster.There are two patterns for creating a custom converter: the basic pattern and the factory pattern. Das Factorymuster ist für Konverter vorgesehen, die den Enum-Typ oder offene generische Typen verarbeiten.The factory pattern is for converters that handle type Enum or open generics. Das grundlegende Muster ist für nicht generische und geschlossene generische Typen gedacht.The basic pattern is for non-generic and closed generic types. Konverter für die folgenden Typen erfordern z. B. das Factorymuster:For example, converters for the following types require the factory pattern:

  • Dictionary<TKey, TValue>
  • Enum
  • List<T>

Einige Beispiele für Typen, die vom grundlegenden Muster verarbeitet werden können, sind u. a.:Some examples of types that can be handled by the basic pattern include:

  • Dictionary<int, string>
  • WeekdaysEnum
  • List<DateTimeOffset>
  • DateTime
  • Int32

Das grundlegende Muster erstellt eine Klasse, die einen Typ verarbeiten kann.The basic pattern creates a class that can handle one type. Das Factorymuster erstellt eine Klasse, die zur Laufzeit bestimmt, welcher spezifische Typ erforderlich ist, und erstellt dynamisch den entsprechenden Konverter.The factory pattern creates a class that determines, at run time, which specific type is required and dynamically creates the appropriate converter.

Grundlegender Konverter – BeispielSample basic converter

Das folgende Beispiel ist ein Konverter, der die Standardserialisierung für einen vorhandenen Datentyp außer Kraft setzt.The following sample is a converter that overrides default serialization for an existing data type. Der Konverter verwendet das Format „mm/dd/yyyy“ für DateTimeOffset-Eigenschaften.The converter uses mm/dd/yyyy format for DateTimeOffset properties.

using System;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
    public class DateTimeOffsetConverter : JsonConverter<DateTimeOffset>
    {
        public override DateTimeOffset Read(
            ref Utf8JsonReader reader,
            Type typeToConvert,
            JsonSerializerOptions options) =>
                DateTimeOffset.ParseExact(reader.GetString(),
                    "MM/dd/yyyy", CultureInfo.InvariantCulture);

        public override void Write(
            Utf8JsonWriter writer,
            DateTimeOffset dateTimeValue,
            JsonSerializerOptions options) =>
                writer.WriteStringValue(dateTimeValue.ToString(
                    "MM/dd/yyyy", CultureInfo.InvariantCulture));
    }
}

Factorymusterkonverter – BeispielSample factory pattern converter

Im folgenden Code wird ein benutzerdefinierter Konverter gezeigt, der mit Dictionary<Enum,TValue> arbeitet.The following code shows a custom converter that works with Dictionary<Enum,TValue>. Der Code folgt dem Factorymuster, da der erste generische Typparameter Enum und der zweite offen ist.The code follows the factory pattern because the first generic type parameter is Enum and the second is open. Die CanConvert-Methode gibt true nur für ein Dictionary mit zwei generischen Parametern zurück, wobei der erste ein Enum-Typ ist.The CanConvert method returns true only for a Dictionary with two generic parameters, the first of which is an Enum type. Der innere Konverter ruft einen vorhandenen Konverter ab, um jeglichen Typ zu verarbeiten, der zur Laufzeit für TValue bereitgestellt wird.The inner converter gets an existing converter to handle whichever type is provided at run time for TValue.

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
    public class DictionaryTKeyEnumTValueConverter : JsonConverterFactory
    {
        public override bool CanConvert(Type typeToConvert)
        {
            if (!typeToConvert.IsGenericType)
            {
                return false;
            }

            if (typeToConvert.GetGenericTypeDefinition() != typeof(Dictionary<,>))
            {
                return false;
            }

            return typeToConvert.GetGenericArguments()[0].IsEnum;
        }

        public override JsonConverter CreateConverter(
            Type type,
            JsonSerializerOptions options)
        {
            Type keyType = type.GetGenericArguments()[0];
            Type valueType = type.GetGenericArguments()[1];

            JsonConverter converter = (JsonConverter)Activator.CreateInstance(
                typeof(DictionaryEnumConverterInner<,>).MakeGenericType(
                    new Type[] { keyType, valueType }),
                BindingFlags.Instance | BindingFlags.Public,
                binder: null,
                args: new object[] { options },
                culture: null);

            return converter;
        }

        private class DictionaryEnumConverterInner<TKey, TValue> :
            JsonConverter<Dictionary<TKey, TValue>> where TKey : struct, Enum
        {
            private readonly JsonConverter<TValue> _valueConverter;
            private Type _keyType;
            private Type _valueType;

            public DictionaryEnumConverterInner(JsonSerializerOptions options)
            {
                // For performance, use the existing converter if available.
                _valueConverter = (JsonConverter<TValue>)options
                    .GetConverter(typeof(TValue));

                // Cache the key and value types.
                _keyType = typeof(TKey);
                _valueType = typeof(TValue);
            }

            public override Dictionary<TKey, TValue> Read(
                ref Utf8JsonReader reader,
                Type typeToConvert,
                JsonSerializerOptions options)
            {
                if (reader.TokenType != JsonTokenType.StartObject)
                {
                    throw new JsonException();
                }

                Dictionary<TKey, TValue> dictionary = new Dictionary<TKey, TValue>();

                while (reader.Read())
                {
                    if (reader.TokenType == JsonTokenType.EndObject)
                    {
                        return dictionary;
                    }

                    // Get the key.
                    if (reader.TokenType != JsonTokenType.PropertyName)
                    {
                        throw new JsonException();
                    }

                    string propertyName = reader.GetString();

                    // For performance, parse with ignoreCase:false first.
                    if (!Enum.TryParse(propertyName, ignoreCase: false, out TKey key) &&
                        !Enum.TryParse(propertyName, ignoreCase: true, out key))
                    {
                        throw new JsonException(
                            $"Unable to convert \"{propertyName}\" to Enum \"{_keyType}\".");
                    }

                    // Get the value.
                    TValue v;
                    if (_valueConverter != null)
                    {
                        reader.Read();
                        v = _valueConverter.Read(ref reader, _valueType, options);
                    }
                    else
                    {
                        v = JsonSerializer.Deserialize<TValue>(ref reader, options);
                    }

                    // Add to dictionary.
                    dictionary.Add(key, v);
                }

                throw new JsonException();
            }

            public override void Write(
                Utf8JsonWriter writer,
                Dictionary<TKey, TValue> dictionary,
                JsonSerializerOptions options)
            {
                writer.WriteStartObject();

                foreach (KeyValuePair<TKey, TValue> kvp in dictionary)
                {
                    writer.WritePropertyName(kvp.Key.ToString());

                    if (_valueConverter != null)
                    {
                        _valueConverter.Write(writer, kvp.Value, options);
                    }
                    else
                    {
                        JsonSerializer.Serialize(writer, kvp.Value, options);
                    }
                }

                writer.WriteEndObject();
            }
        }
    }
}

Der vorangehende Code ist identisch mit dem, der weiter unten in diesem Artikel unter Unterstützung für Wörterbücher mit Schlüsseln, die keine Zeichenfolgen sind gezeigt wird.The preceding code is the same as what is shown in the Support Dictionary with non-string key later in this article.

Schritte zum Einhalten des grundlegenden MustersSteps to follow the basic pattern

Die folgenden Schritte erläutern, wie Sie einen Konverter erstellen, indem Sie dem grundlegenden Muster folgen:The following steps explain how to create a converter by following the basic pattern:

  • Erstellen Sie eine Klasse, die von JsonConverter<T> abgeleitet ist, wobei T der Typ ist, der serialisiert und deserialisiert werden soll.Create a class that derives from JsonConverter<T> where T is the type to be serialized and deserialized.
  • Setzen Sie die Read-Methode außer Kraft, um den eingehenden JSON-Code zu deserialisieren und in den Typ T zu konvertieren.Override the Read method to deserialize the incoming JSON and convert it to type T. Verwenden Sie den Utf8JsonReader, der an die Methode übergeben wird, um den JSON-Code zu lesen.Use the Utf8JsonReader that is passed to the method to read the JSON.
  • Setzen Sie die Write-Methode außer Kraft, um das eingehende Objekt vom Typ T zu serialisieren.Override the Write method to serialize the incoming object of type T. Verwenden Sie den Utf8JsonWriter, der an die Methode übergeben wird, um den JSON-Code zu schreiben.Use the Utf8JsonWriter that is passed to the method to write the JSON.
  • Setzen Sie die CanConvert-Methode nur außer Kraft, wenn dies notwendig ist.Override the CanConvert method only if necessary. Die Standardimplementierung gibt true zurück, wenn der zu konvertierende Typ vom Typ T ist.The default implementation returns true when the type to convert is of type T. Daher müssen Konverter, die nur den Typ T unterstützen, diese Methode nicht außer Kraft setzen.Therefore, converters that support only type T don't need to override this method. Ein Beispiel für einen Konverter, der diese Methode außer Kraft setzen muss, finden Sie im weiter unten in diesem Artikel im Abschnitt Polymorphe Deserialisierung.For an example of a converter that does need to override this method, see the polymorphic deserialization section later in this article.

Sie können den Quellcode des integrierten Konverters als Referenzimplementierungen zum Schreiben von benutzerdefinierten Konvertern verwenden.You can refer to the built-in converters source code as reference implementations for writing custom converters.

Schritte zum Einhalten des FactorymustersSteps to follow the factory pattern

Die folgenden Schritte erläutern, wie Sie einen Konverter erstellen, indem Sie dem Factorymuster folgen:The following steps explain how to create a converter by following the factory pattern:

  • Erstellen Sie eine von der JsonConverterFactory-Klasse abgeleitete Klasse.Create a class that derives from JsonConverterFactory.
  • Überschreiben Sie die CanConvert-Methode, um „true“ zurückzugeben, wenn der zu konvertierende Typ einer ist, den der Konverter verarbeiten kann.Override the CanConvert method to return true when the type to convert is one that the converter can handle. Wenn der Konverter z. B. für List<T> ist, kann er eventuell nur List<int>, List<string> und List<DateTime> verarbeiten.For example, if the converter is for List<T> it might only handle List<int>, List<string>, and List<DateTime>.
  • Setzen Sie die CreateConverter-Methode außer Kraft, um eine Instanz einer Konverterklasse zurückzugeben, die den zur Laufzeit bereitgestellten zu konvertierenden Typ verarbeitet.Override the CreateConverter method to return an instance of a converter class that will handle the type-to-convert that is provided at run time.
  • Erstellen Sie die Konverterklasse, die von der CreateConverter-Methode instanziiert wird.Create the converter class that the CreateConverter method instantiates.

Das Factorymuster ist für offene generische Typen erforderlich, da der Code, mit dem ein Objekt in eine und aus einer Zeichenfolge konvertiert werden soll, nicht für alle Typen identisch ist.The factory pattern is required for open generics because the code to convert an object to and from a string isn't the same for all types. Ein Konverter für einen offenen generischen Typ (z. B. List<T>) muss verdeckt einen Konverter für einen geschlossenen generischen Typ erstellen (z. B. List<DateTime>).A converter for an open generic type (List<T>, for example) has to create a converter for a closed generic type (List<DateTime>, for example) behind the scenes. Es muss Code geschrieben werden, um jeden geschlossenen generischen Typ zu verarbeiten, den der Konverter verarbeiten kann.Code must be written to handle each closed-generic type that the converter can handle.

Der Typ Enum ähnelt einem offenen generischen Typ: Ein Konverter für Enum muss verdeckt einen Konverter für einen spezifischen Enum (z. B. WeekdaysEnum) erstellen.The Enum type is similar to an open generic type: a converter for Enum has to create a converter for a specific Enum (WeekdaysEnum, for example) behind the scenes.

FehlerbehandlungError handling

Wenn Sie im Fehlerbehandlungscode eine Ausnahme auslösen müssen, sollten Sie erwägen, eine JsonException ohne Meldung auszulösen.If you need to throw an exception in error-handling code, consider throwing a JsonException without a message. Dieser Ausnahmetyp erstellt automatisch eine Meldung, die den Pfad zu dem Teil des JSON-Codes enthält, der den Fehler verursacht hat.This exception type automatically creates a message that includes the path to the part of the JSON that caused the error. Beispielsweise erzeugt die Anweisung throw new JsonException(); eine Fehlermeldung wie im folgenden Beispiel:For example, the statement throw new JsonException(); produces an error message like the following example:

Unhandled exception. System.Text.Json.JsonException:
The JSON value could not be converted to System.Object.
Path: $.Date | LineNumber: 1 | BytePositionInLine: 37.

Wenn Sie eine Meldung angeben (z. B. throw new JsonException("Error occurred")), stellt die Ausnahme immer noch den Pfad in der Path-Eigenschaft bereit.If you do provide a message (for example, throw new JsonException("Error occurred"), the exception still provides the path in the Path property.

Registrieren eines benutzerdefinierten KonvertersRegister a custom converter

Registrieren Sie einen benutzerdefinierten Konverter, damit die Methoden Serialize und Deserialize diesen verwenden.Register a custom converter to make the Serialize and Deserialize methods use it. Wählen Sie einen der folgenden Ansätze aus:Choose one of the following approaches:

Registrierungsbeispiel – Converters-SammlungRegistration sample - Converters collection

Im Folgenden finden Sie ein Beispiel, mit dem der DateTimeOffsetConverter zum Standard für Eigenschaften vom Typ DateTimeOffset gemacht wird:Here's an example that makes the DateTimeOffsetConverter the default for properties of type DateTimeOffset:

var serializeOptions = new JsonSerializerOptions();
serializeOptions.Converters.Add(new DateTimeOffsetConverter());
serializeOptions.WriteIndented = true;
jsonString = JsonSerializer.Serialize(weatherForecast, serializeOptions);

Angenommen, Sie serialisieren eine Instanz des folgenden Typs:Suppose you serialize an instance of the following type:

public class WeatherForecast
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string Summary { get; set; }
}

Hier sehen Sie ein Beispiel für eine JSON-Ausgabe, die anzeigt, dass der benutzerdefinierte Konverter verwendet wurde:Here's an example of JSON output that shows the custom converter was used:

{
  "Date": "08/01/2019",
  "TemperatureCelsius": 25,
  "Summary": "Hot"
}

Der folgende Code verwendet denselben Ansatz zum Deserialisieren mithilfe des benutzerdefinierten DateTimeOffset-Konverters:The following code uses the same approach to deserialize using the custom DateTimeOffset converter:

var deserializeOptions = new JsonSerializerOptions();
deserializeOptions.Converters.Add(new DateTimeOffsetConverter());
weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(jsonString, deserializeOptions);

Registrierungsbeispiel – [JsonConverter]-Attribut für eine EigenschaftRegistration sample - [JsonConverter] on a property

Im folgenden Code wird ein benutzerdefinierter Konverter für die Date-Eigenschaft ausgewählt:The following code selects a custom converter for the Date property:

public class WeatherForecastWithConverterAttribute
{
    [JsonConverter(typeof(DateTimeOffsetConverter))]
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string Summary { get; set; }
}

Der Code zum Serialisieren von WeatherForecastWithConverterAttribute erfordert nicht die Verwendung von JsonSerializeOptions.Converters:The code to serialize WeatherForecastWithConverterAttribute doesn't require the use of JsonSerializeOptions.Converters:

var serializeOptions = new JsonSerializerOptions();
serializeOptions.WriteIndented = true;
jsonString = JsonSerializer.Serialize(weatherForecast, serializeOptions);

Der Code zum Deserialisieren erfordert ebenfalls nicht die Verwendung von Converters:The code to deserialize also doesn't require the use of Converters:

weatherForecast = JsonSerializer.Deserialize<WeatherForecastWithConverterAttribute>(jsonString);

Registrierungsbeispiel – [JsonConverter]-Attribut für einen TypRegistration sample - [JsonConverter] on a type

Der folgende Code erstellt eine Struktur und wendet das [JsonConverter]-Attribut darauf an:Here's code that creates a struct and applies the [JsonConverter] attribute to it:

using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
    [JsonConverter(typeof(TemperatureConverter))]
    public struct Temperature
    {
        public Temperature(int degrees, bool celsius)
        {
            _degrees = degrees;
            _isCelsius = celsius;
        }
        private bool _isCelsius;
        private int _degrees;
        public int Degrees => _degrees;
        public bool IsCelsius => _isCelsius;
        public bool IsFahrenheit => !_isCelsius;
        public override string ToString() =>
            $"{_degrees.ToString()}{(_isCelsius ? "C" : "F")}";
        public static Temperature Parse(string input)
        {
            int degrees = int.Parse(input.Substring(0, input.Length - 1));
            bool celsius = (input.Substring(input.Length - 1) == "C");
            return new Temperature(degrees, celsius);
        }
    }
}

Hier sehen Sie den benutzerdefinierten Konverter für die vorangehende Struktur:Here's the custom converter for the preceding struct:

using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
    public class TemperatureConverter : JsonConverter<Temperature>
    {
        public override Temperature Read(
            ref Utf8JsonReader reader,
            Type typeToConvert,
            JsonSerializerOptions options) =>
                Temperature.Parse(reader.GetString());

        public override void Write(
            Utf8JsonWriter writer,
            Temperature temperature,
            JsonSerializerOptions options) =>
                writer.WriteStringValue(temperature.ToString());
    }
}

Das [JsonConvert]-Attribut der Struktur registriert den benutzerdefinierten Konverter als Standard für Eigenschaften vom Typ Temperature.The [JsonConvert] attribute on the struct registers the custom converter as the default for properties of type Temperature. Der Konverter wird automatisch für die TemperatureCelsius-Eigenschaft des folgenden Typs verwendet, wenn Sie sie serialisieren oder deserialisieren:The converter is automatically used on the TemperatureCelsius property of the following type when you serialize or deserialize it:

public class WeatherForecastWithTemperatureStruct
{
    public DateTimeOffset Date { get; set; }
    public Temperature TemperatureCelsius { get; set; }
    public string Summary { get; set; }
}

Rangfolge für die Registrierung von KonverternConverter registration precedence

Während der Serialisierung oder Deserialisierung wird für jedes JSON-Element ein Konverter in der folgenden Reihenfolge ausgewählt, wobei die Auflistung von der höchsten Priorität zur niedrigsten erfolgt:During serialization or deserialization, a converter is chosen for each JSON element in the following order, listed from highest priority to lowest:

  • Auf eine Eigenschaft angewendeter [JsonConverter].[JsonConverter] applied to a property.
  • Ein der Converters-Sammlung hinzugefügt er Konverter.A converter added to the Converters collection.
  • Auf einen benutzerdefinierten Werttyp oder ein POCO angewendeter [JsonConverter].[JsonConverter] applied to a custom value type or POCO.

Wenn mehrere benutzerdefinierte Konverter für einen Typ in der Converters-Sammlung registriert sind, wird der erste Konverter verwendet, der für CanConvert „true“ zurückgibt.If multiple custom converters for a type are registered in the Converters collection, the first converter that returns true for CanConvert is used.

Ein integrierter Konverter wird nur ausgewählt, wenn kein anwendbarer benutzerdefinierter Konverter registriert ist.A built-in converter is chosen only if no applicable custom converter is registered.

Konverterbeispiele für gängige SzenarienConverter samples for common scenarios

In den folgenden Abschnitten werden Konverterbeispiele bereitgestellt, in denen einige gängige Szenarien behandelt werden, die von integrierten Funktionen nicht verarbeitet werden.The following sections provide converter samples that address some common scenarios that built-in functionality doesn't handle.

Deserialisieren abgeleiteter Typen in ObjekteigenschaftenDeserialize inferred types to object properties

Beim Deserialisieren in eine Eigenschaft vom Typ object wird ein JsonElement Objekt erstellt.When deserializing to a property of type object, a JsonElement object is created. Der Grund dafür ist, dass der Deserialisierer nicht weiß, welcher CLR-Typ erstellt werden soll, und nicht versucht, diesen vorherzusagen.The reason is that the deserializer doesn't know what CLR type to create, and it doesn't try to guess. Wenn z. B. eine JSON-Eigenschaft den Wert „true“ hat, leitet der Deserialisierer nicht ab, dass der Wert vom Typ Boolean ist, und wenn ein Element „01/01/2019“ aufweist, leitet der Deserialisierer nicht ab, dass es sich um einen DateTime-Typ handelt.For example, if a JSON property has "true", the deserializer doesn't infer that the value is a Boolean, and if an element has "01/01/2019", the deserializer doesn't infer that it's a DateTime.

Typableitung kann ungenau sein.Type inference can be inaccurate. Wenn der Deserialisierer eine JSON-Zahl, die kein Dezimaltrennzeichen aufweist, als long analysiert, kann dies zu einem „außerhalb des zulässigen Bereichs“-Probleme führen, wenn der Wert ursprünglich als ulong oder BigInteger serialisiert wurde.If the deserializer parses a JSON number that has no decimal point as a long, that might result in out-of-range issues if the value was originally serialized as a ulong or BigInteger. Wird eine Zahl, die ein Dezimaltrennzeichen aufweist, als double analysiert, kann hierdurch die Genauigkeit verloren gehen, wenn die Zahl ursprünglich als decimal serialisiert wurde.Parsing a number that has a decimal point as a double might lose precision if the number was originally serialized as a decimal.

Für Szenarien, die eine Typableitung erfordern, zeigt der folgende Code einen benutzerdefinierten Konverter für object-Eigenschaften an.For scenarios that require type inference, the following code shows a custom converter for object properties. Der Code konvertiert Folgendes:The code converts:

  • true un false in Booleantrue and false to Boolean
  • Zahlen ohne Dezimaltrennzeichen in longNumbers without a decimal to long
  • Zahlen mit Dezimaltrennzeichen in doubleNumbers with a decimal to double
  • Datumsangaben in DateTimeDates to DateTime
  • Zeichenfolgen in stringStrings to string
  • Alles andere in JsonElementEverything else to JsonElement
using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
    public class ObjectToInferredTypesConverter
        : JsonConverter<object>
    {
        public override object Read(
            ref Utf8JsonReader reader,
            Type typeToConvert,
            JsonSerializerOptions options)
        {
            if (reader.TokenType == JsonTokenType.True)
            {
                return true;
            }

            if (reader.TokenType == JsonTokenType.False)
            {
                return false;
            }

            if (reader.TokenType == JsonTokenType.Number)
            {
                if (reader.TryGetInt64(out long l))
                {
                    return l;
                }

                return reader.GetDouble();
            }

            if (reader.TokenType == JsonTokenType.String)
            {
                if (reader.TryGetDateTime(out DateTime datetime))
                {
                    return datetime;
                }

                return reader.GetString();
            }

            // Use JsonElement as fallback.
            // Newtonsoft uses JArray or JObject.
            using JsonDocument document = JsonDocument.ParseValue(ref reader);
            return document.RootElement.Clone();
        }

        public override void Write(
            Utf8JsonWriter writer,
            object objectToWrite,
            JsonSerializerOptions options) =>
                throw new InvalidOperationException("Should not get here.");
    }
}

Der folgende Code registriert den Konverter:The following code registers the converter:

var deserializeOptions = new JsonSerializerOptions();
deserializeOptions.Converters.Add(new ObjectToInferredTypesConverter());

Hier ist ein Beispieltyp mit object-Eigenschaften:Here's an example type with object properties:

public class WeatherForecastWithObjectProperties
{
    public object Date { get; set; }
    public object TemperatureCelsius { get; set; }
    public object Summary { get; set; }
}

Das folgende Beispiel für zu deserialisierenden JSON-Code enthält Werte, die als DateTime, long und string deserialisiert werden:The following example of JSON to deserialize contains values that will be deserialized as DateTime, long, and string:

{
  "Date": "2019-08-01T00:00:00-07:00",
  "TemperatureCelsius": 25,
  "Summary": "Hot",
}

Ohne den benutzerdefinierten Konverter fügt die Deserialisierung eine JsonElement in jede Eigenschaft ein.Without the custom converter, deserialization puts a JsonElement in each property.

Der Ordner unit tests (Komponententests) im System.Text.Json.Serialization-Namespace enthält weitere Beispiele für benutzerdefinierte Konverter, die die Deserialisierung in object-Eigenschaften verarbeiten.The unit tests folder in the System.Text.Json.Serialization namespace has more examples of custom converters that handle deserialization to object properties.

Unterstützung für Wörterbücher mit Schlüsseln, die keine Zeichenfolgen sindSupport Dictionary with non-string key

Die integrierte Unterstützung für Wörterbuchsammlungen ist für Dictionary<string, TValue>.The built-in support for dictionary collections is for Dictionary<string, TValue>. Das heißt, der Schlüssel muss eine Zeichenfolge sein.That is, the key must be a string. Um ein Wörterbuch mit einem Schlüssel vom Typ „integer“ (Ganzzahl) oder einem anderen Typ zu unterstützen, ist ein benutzerdefinierter Konverter erforderlich.To support a dictionary with an integer or some other type as the key, a custom converter is required.

Im folgenden Code wird ein benutzerdefinierter Konverter gezeigt, der mit Dictionary<Enum,TValue> arbeitet:The following code shows a custom converter that works with Dictionary<Enum,TValue>:

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
    public class DictionaryTKeyEnumTValueConverter : JsonConverterFactory
    {
        public override bool CanConvert(Type typeToConvert)
        {
            if (!typeToConvert.IsGenericType)
            {
                return false;
            }

            if (typeToConvert.GetGenericTypeDefinition() != typeof(Dictionary<,>))
            {
                return false;
            }

            return typeToConvert.GetGenericArguments()[0].IsEnum;
        }

        public override JsonConverter CreateConverter(
            Type type,
            JsonSerializerOptions options)
        {
            Type keyType = type.GetGenericArguments()[0];
            Type valueType = type.GetGenericArguments()[1];

            JsonConverter converter = (JsonConverter)Activator.CreateInstance(
                typeof(DictionaryEnumConverterInner<,>).MakeGenericType(
                    new Type[] { keyType, valueType }),
                BindingFlags.Instance | BindingFlags.Public,
                binder: null,
                args: new object[] { options },
                culture: null);

            return converter;
        }

        private class DictionaryEnumConverterInner<TKey, TValue> :
            JsonConverter<Dictionary<TKey, TValue>> where TKey : struct, Enum
        {
            private readonly JsonConverter<TValue> _valueConverter;
            private Type _keyType;
            private Type _valueType;

            public DictionaryEnumConverterInner(JsonSerializerOptions options)
            {
                // For performance, use the existing converter if available.
                _valueConverter = (JsonConverter<TValue>)options
                    .GetConverter(typeof(TValue));

                // Cache the key and value types.
                _keyType = typeof(TKey);
                _valueType = typeof(TValue);
            }

            public override Dictionary<TKey, TValue> Read(
                ref Utf8JsonReader reader,
                Type typeToConvert,
                JsonSerializerOptions options)
            {
                if (reader.TokenType != JsonTokenType.StartObject)
                {
                    throw new JsonException();
                }

                Dictionary<TKey, TValue> dictionary = new Dictionary<TKey, TValue>();

                while (reader.Read())
                {
                    if (reader.TokenType == JsonTokenType.EndObject)
                    {
                        return dictionary;
                    }

                    // Get the key.
                    if (reader.TokenType != JsonTokenType.PropertyName)
                    {
                        throw new JsonException();
                    }

                    string propertyName = reader.GetString();

                    // For performance, parse with ignoreCase:false first.
                    if (!Enum.TryParse(propertyName, ignoreCase: false, out TKey key) &&
                        !Enum.TryParse(propertyName, ignoreCase: true, out key))
                    {
                        throw new JsonException(
                            $"Unable to convert \"{propertyName}\" to Enum \"{_keyType}\".");
                    }

                    // Get the value.
                    TValue v;
                    if (_valueConverter != null)
                    {
                        reader.Read();
                        v = _valueConverter.Read(ref reader, _valueType, options);
                    }
                    else
                    {
                        v = JsonSerializer.Deserialize<TValue>(ref reader, options);
                    }

                    // Add to dictionary.
                    dictionary.Add(key, v);
                }

                throw new JsonException();
            }

            public override void Write(
                Utf8JsonWriter writer,
                Dictionary<TKey, TValue> dictionary,
                JsonSerializerOptions options)
            {
                writer.WriteStartObject();

                foreach (KeyValuePair<TKey, TValue> kvp in dictionary)
                {
                    writer.WritePropertyName(kvp.Key.ToString());

                    if (_valueConverter != null)
                    {
                        _valueConverter.Write(writer, kvp.Value, options);
                    }
                    else
                    {
                        JsonSerializer.Serialize(writer, kvp.Value, options);
                    }
                }

                writer.WriteEndObject();
            }
        }
    }
}

Der folgende Code registriert den Konverter:The following code registers the converter:

var serializeOptions = new JsonSerializerOptions();
serializeOptions.Converters.Add(new DictionaryTKeyEnumTValueConverter());

Der Konverter kann die TemperatureRanges-Eigenschaft der folgenden Klasse serialisieren und deserialisieren, die die folgende Enum verwendet:The converter can serialize and deserialize the TemperatureRanges property of the following class that uses the following Enum:

public class WeatherForecastWithEnumDictionary
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string Summary { get; set; }
    public Dictionary<SummaryWordsEnum, int> TemperatureRanges { get; set; }
}

public enum SummaryWordsEnum
{
    Cold, Hot
}

Die JSON-Ausgabe der Serialisierung sieht wie im folgenden Beispiel aus:The JSON output from serialization looks like the following example:

{
  "Date": "2019-08-01T00:00:00-07:00",
  "TemperatureCelsius": 25,
  "Summary": "Hot",
  "TemperatureRanges": {
    "Cold": 20,
    "Hot": 40
  }
}

Der Ordner unit tests (Komponententests) im System.Text.Json.Serialization-Namespace enthält weitere Beispiele für benutzerdefinierte Konverter, die Wörterbücher mit Schlüsseln, die keine Zeichenfolgen sind, verarbeiten.The unit tests folder in the System.Text.Json.Serialization namespace has more examples of custom converters that handle non-string-key dictionaries.

Unterstützung polymorpher DeserialisierungSupport polymorphic deserialization

Integrierte Funktionen bieten einen begrenzten Funktionsbereich der polymorphen Serialisierung, aber gar keine Unterstützung für Deserialisierung.Built-in features provide a limited range of polymorphic serialization but no support for deserialization at all. Die Deserialisierung erfordert einen benutzerdefinierten Konverter.Deserialization requires a custom converter.

Angenommen, Sie verfügen beispielsweise über eine abstrakte Basisklasse Person mit den abgeleiteten Klassen Employee und Customer.Suppose, for example, you have a Person abstract base class, with Employee and Customer derived classes. Polymorphe Deserialisierung bedeutet, dass Sie zur Entwurfszeit Person als Deserialisierungsziel angeben können, und dass die Objekte Customer und Employee im JSON-Code zur Laufzeit ordnungsgemäß deserialisiert werden.Polymorphic deserialization means that at design time you can specify Person as the deserialization target, and Customer and Employee objects in the JSON are correctly deserialized at run time. Während der Deserialisierung müssen Sie nach Hinweisen suchen, die den erforderlichen Typ in JSON identifizieren.During deserialization, you have to find clues that identify the required type in the JSON. Die Arten der verfügbaren Hinweise variieren in jedem Szenario.The kinds of clues available vary with each scenario. Beispielsweise kann eine Diskriminatoreigenschaft verfügbar sein, oder Sie müssen sich darauf verlassen, dass eine bestimmte Eigenschaft vorhanden oder nicht vorhanden ist.For example, a discriminator property might be available or you might have to rely on the presence or absence of a particular property. Das aktuelle Release von System.Text.Json stellt keine Attribute bereit, um anzugeben, wie polymorphe Deserialisierungsszenarien behandelt werden sollen, sodass benutzerdefinierte Konverter erforderlich sind.The current release of System.Text.Json doesn't provide attributes to specify how to handle polymorphic deserialization scenarios, so custom converters are required.

Der folgende Code zeigt eine Basisklasse, zwei abgeleitete Klassen und einen benutzerdefinierten Konverter für diese.The following code shows a base class, two derived classes, and a custom converter for them. Der Konverter verwendet eine Diskriminatoreigenschaft, um die polymorphe Deserialisierung durchzuführen.The converter uses a discriminator property to do polymorphic deserialization. Der Typdiskriminator befindet sich nicht in den Klassendefinitionen, wird aber während der Serialisierung erstellt und während der Deserialisierung gelesen.The type discriminator isn't in the class definitions but is created during serialization and is read during deserialization.

public class Person
{
    public string Name { get; set; }
}

public class Customer : Person
{
    public decimal CreditLimit { get; set; }
}

public class Employee : Person
{
    public string OfficeNumber { get; set; }
}
using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
    public class PersonConverterWithTypeDiscriminator : JsonConverter<Person>
    {
        enum TypeDiscriminator
        {
            Customer = 1,
            Employee = 2
        }

        public override bool CanConvert(Type typeToConvert) =>
            typeof(Person).IsAssignableFrom(typeToConvert);

        public override Person Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            if (reader.TokenType != JsonTokenType.StartObject)
            {
                throw new JsonException();
            }

            reader.Read();
            if (reader.TokenType != JsonTokenType.PropertyName)
            {
                throw new JsonException();
            }

            string propertyName = reader.GetString();
            if (propertyName != "TypeDiscriminator")
            {
                throw new JsonException();
            }

            reader.Read();
            if (reader.TokenType != JsonTokenType.Number)
            {
                throw new JsonException();
            }

            TypeDiscriminator typeDiscriminator = (TypeDiscriminator)reader.GetInt32();
            Person person = typeDiscriminator switch
            {
                TypeDiscriminator.Customer => new Customer(),
                TypeDiscriminator.Employee => new Employee(),
                _ => throw new JsonException()
            };

            while (reader.Read())
            {
                if (reader.TokenType == JsonTokenType.EndObject)
                {
                    return person;
                }

                if (reader.TokenType == JsonTokenType.PropertyName)
                {
                    propertyName = reader.GetString();
                    reader.Read();
                    switch (propertyName)
                    {
                        case "CreditLimit":
                            decimal creditLimit = reader.GetDecimal();
                            ((Customer)person).CreditLimit = creditLimit;
                            break;
                        case "OfficeNumber":
                            string officeNumber = reader.GetString();
                            ((Employee)person).OfficeNumber = officeNumber;
                            break;
                        case "Name":
                            string name = reader.GetString();
                            person.Name = name;
                            break;
                    }
                }
            }

            throw new JsonException();
        }

        public override void Write(Utf8JsonWriter writer, Person person, JsonSerializerOptions options)
        {
            writer.WriteStartObject();

            if (person is Customer customer)
            {
                writer.WriteNumber("TypeDiscriminator", (int)TypeDiscriminator.Customer);
                writer.WriteNumber("CreditLimit", customer.CreditLimit);
            }
            else if (person is Employee employee)
            {
                writer.WriteNumber("TypeDiscriminator", (int)TypeDiscriminator.Employee);
                writer.WriteString("OfficeNumber", employee.OfficeNumber);
            }

            writer.WriteString("Name", person.Name);

            writer.WriteEndObject();
        }
    }
}

Der folgende Code registriert den Konverter:The following code registers the converter:

var serializeOptions = new JsonSerializerOptions();
serializeOptions.Converters.Add(new PersonConverterWithTypeDiscriminator());

Das JSON kann mit demselben Konverter deserialisiert werde, mit dem es auch serialisiert wurde, z. B.:The converter can deserialize JSON that was created by using the same converter to serialize, for example:

[
  {
    "TypeDiscriminator": 1,
    "CreditLimit": 10000,
    "Name": "John"
  },
  {
    "TypeDiscriminator": 2,
    "OfficeNumber": "555-1234",
    "Name": "Nancy"
  }
]

Mit dem Konvertercode im vorherigen Beispiel wird jede Eigenschaft manuell gelesen und geschrieben.The converter code in the preceding example reads and writes each property manually. Eine Alternative besteht darin, Deserialize oder Serialize aufzurufen, um einen Teil der Arbeit zu erledigen.An alternative is to call Deserialize or Serialize to do some of the work. Ein Beispiel hierzu finden Sie in diesem StackOverflow-Beitrag.For an example, see this StackOverflow post.

Unterstützung von Roundtrips für Stack<T>.Support round-trip for Stack<T>

Wenn Sie eine JSON-Zeichenfolge in ein Stack<T>-Objekt deserialisieren und dieses Objekt anschließend serialisieren, ist die Reihenfolge des Stapelinhalts umgekehrt.If you deserialize a JSON string into a Stack<T> object and then serialize that object, the contents of the stack are in reverse order. Dieses Verhalten gilt für die folgenden Typen und Schnittstellen sowie für benutzerdefinierte Typen, die von ihnen abgeleitet werden:This behavior applies to the following types and interface, and user-defined types that derive from them:

Um die Serialisierung und Deserialisierung zu unterstützen, die die ursprüngliche Reihenfolge im Stapel beibehält, ist ein benutzerdefinierter Konverter erforderlich.To support serialization and deserialization that retains the original order in the stack, a custom converter is required.

Der folgende Code zeigt einen benutzerdefinierten Konverter, der Roundtrips zu und von Stack<T>-Objekten ermöglicht:The following code shows a custom converter that enables round-tripping to and from Stack<T> objects:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
    public class JsonConverterFactoryForStackOfT : JsonConverterFactory
    {
        public override bool CanConvert(Type typeToConvert)
        {
            return typeToConvert.IsGenericType &&
                typeToConvert.GetGenericTypeDefinition() == typeof(Stack<>);
        }

        public override JsonConverter CreateConverter(
            Type typeToConvert, JsonSerializerOptions options)
        {
            Debug.Assert(typeToConvert.IsGenericType &&
                typeToConvert.GetGenericTypeDefinition() == typeof(Stack<>));

            Type elementType = typeToConvert.GetGenericArguments()[0];

            JsonConverter converter = (JsonConverter)Activator.CreateInstance(
                typeof(JsonConverterForStackOfT<>)
                    .MakeGenericType(new Type[] { elementType }),
                BindingFlags.Instance | BindingFlags.Public,
                binder: null,
                args: null,
                culture: null)!;

            return converter;
        }
    }

    public class JsonConverterForStackOfT<T> : JsonConverter<Stack<T>>
    {
        public override Stack<T> Read(
            ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            if (reader.TokenType != JsonTokenType.StartArray || !reader.Read())
            {
                throw new JsonException();
            }

            var elements = new Stack<T>();

            while (reader.TokenType != JsonTokenType.EndArray)
            {
                elements.Push(JsonSerializer.Deserialize<T>(ref reader, options));

                if (!reader.Read())
                {
                    throw new JsonException();
                }
            }

            return elements;
        }

        public override void Write(
            Utf8JsonWriter writer, Stack<T> value, JsonSerializerOptions options)
        {
            writer.WriteStartArray();

            var reversed = new Stack<T>(value);

            foreach (T item in reversed)
            {
                JsonSerializer.Serialize(writer, item, options);
            }

            writer.WriteEndArray();
        }
    }
}

Der folgende Code registriert den Konverter:The following code registers the converter:

var options = new JsonSerializerOptions
{
    Converters = { new JsonConverterFactoryForStackOfT() },
};

Weitere Beispiele für benutzerdefinierte KonverterOther custom converter samples

Im Artikel Migrieren von Newtonsoft.Json zu System.Text.Json finden Sie zusätzliche Beispiele für benutzerdefinierte Konverter.The Migrate from Newtonsoft.Json to System.Text.Json article contains additional samples of custom converters.

Der Ordner unit tests (Komponententests) im System.Text.Json.Serialization-Quellcode enthält weitere Beispiele für benutzerdefinierte Konverter, wie z. B.:The unit tests folder in the System.Text.Json.Serialization source code includes other custom converter samples, such as:

Wenn Sie einen Konverter erstellen müssen, der das Verhalten eines vorhandenen integrierten Konverters ändert, können Sie den Quellcode des vorhandenen Konverters abrufen, um diesen als Ausgangspunkt für die Anpassung zu verwenden.If you need to make a converter that modifies the behavior of an existing built-in converter, you can get the source code of the existing converter to serve as a starting point for customization.

Zusätzliche RessourcenAdditional resources