Come usare la generazione di origine in System.Text.Json

La generazione di origine in System.Text.Json è disponibile in .NET 6 e versioni successive. Se usata in un'app, la versione del linguaggio dell'app deve essere C# 9.0 o successiva. Questo articolo illustra come usare la serializzazione supportata dalla generazione di origine nelle app.

Per informazioni sulle diverse modalità di generazione di origine, vedere Modalità di generazione di origine.

Usare le impostazioni predefinite della generazione di origine

Per usare la generazione di origine con tutte le impostazioni predefinite (entrambe le modalità, opzioni predefinite):

  1. Creare una classe parziale che deriva da JsonSerializerContext.

  2. Specificare il tipo da serializzare o deserializzare applicando JsonSerializableAttribute alla classe di contesto.

  3. Chiamare un metodo JsonSerializer che:

Per impostazione predefinita, vengono utilizzate entrambe le modalità di generazione di origine, se non se ne specifica una. Per informazioni su come specificare la modalità da usare, vedere Specificare la modalità di generazione di origine più avanti in questo articolo.

Ecco il tipo usato negli esempi seguenti:

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

Ecco la classe di contesto configurata per eseguire la generazione di origine per la classe WeatherForecast precedente:

[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(WeatherForecast))]
internal partial class SourceGenerationContext : JsonSerializerContext
{
}

I tipi di membri WeatherForecast non devono essere specificati in modo esplicito con gli attributi [JsonSerializable]. I membri dichiarati come object sono un'eccezione a questa regola. È necessario specificare il tipo di runtime per un membro dichiarato come object. Si supponga ad esempio di disporre della classe seguente:

public class WeatherForecast
{
    public object? Data { get; set; }
    public List<object>? DataList { get; set; }
}

E si sa che in fase di esecuzione potrebbe avere gli oggetti boolean e int:

WeatherForecast wf = new() { Data = true, DataList = new List<object> { true, 1 } };

Quindi boolean e int devono essere dichiarati come [JsonSerializable]:

[JsonSerializable(typeof(WeatherForecast))]
[JsonSerializable(typeof(bool))]
[JsonSerializable(typeof(int))]
public partial class WeatherForecastContext : JsonSerializerContext
{
}

Per specificare la generazione di origine per una raccolta, usare [JsonSerializable] con il tipo di raccolta. Ad esempio: [JsonSerializable(typeof(List<WeatherForecast>))].

Metodi JsonSerializer che usano la generazione di origine

Negli esempi seguenti la proprietà statica Default del tipo di contesto fornisce un'istanza del tipo di contesto con le opzioni predefinite. L'istanza di contesto fornisce una proprietà WeatherForecast che restituisce un'istanza di JsonTypeInfo<WeatherForecast>. È possibile specificare un nome diverso per questa proprietà usando la proprietà TypeInfoPropertyName dell'attributo [JsonSerializable].

Esempi di serializzazione

Utilizzo di JsonTypeInfo<T>:

jsonString = JsonSerializer.Serialize(
    weatherForecast!, SourceGenerationContext.Default.WeatherForecast);

Utilizzo di JsonSerializerContext:

jsonString = JsonSerializer.Serialize(
    weatherForecast, typeof(WeatherForecast), SourceGenerationContext.Default);

Utilizzo di JsonSerializerOptions:

sourceGenOptions = new JsonSerializerOptions
{
    TypeInfoResolver = SourceGenerationContext.Default
};

jsonString = JsonSerializer.Serialize(
    weatherForecast, typeof(WeatherForecast), sourceGenOptions);

Esempi di deserializzazione

Utilizzo di JsonTypeInfo<T>:

weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(
    jsonString, SourceGenerationContext.Default.WeatherForecast);

Utilizzo di JsonSerializerContext:

weatherForecast = JsonSerializer.Deserialize(
    jsonString, typeof(WeatherForecast), SourceGenerationContext.Default)
    as WeatherForecast;

Utilizzo di JsonSerializerOptions:

var sourceGenOptions = new JsonSerializerOptions
{
    TypeInfoResolver = SourceGenerationContext.Default
};
weatherForecast = JsonSerializer.Deserialize(
    jsonString, typeof(WeatherForecast), sourceGenOptions)
    as WeatherForecast;

Esempio di un programma completo

Ecco gli esempi precedenti in un programma completo:

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

namespace BothModesNoOptions
{
    public class WeatherForecast
    {
        public DateTime Date { get; set; }
        public int TemperatureCelsius { get; set; }
        public string? Summary { get; set; }
    }

    [JsonSourceGenerationOptions(WriteIndented = true)]
    [JsonSerializable(typeof(WeatherForecast))]
    internal partial class SourceGenerationContext : JsonSerializerContext
    {
    }

    public class Program
    {
        public static void Main()
        {
            string jsonString = """
                {
                    "Date": "2019-08-01T00:00:00",
                    "TemperatureCelsius": 25,
                    "Summary": "Hot"
                }
                """;
            WeatherForecast? weatherForecast;

            weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(
                jsonString, SourceGenerationContext.Default.WeatherForecast);
            Console.WriteLine($"Date={weatherForecast?.Date}");
            // output:
            //Date=8/1/2019 12:00:00 AM

            weatherForecast = JsonSerializer.Deserialize(
                jsonString, typeof(WeatherForecast), SourceGenerationContext.Default)
                as WeatherForecast;
            Console.WriteLine($"Date={weatherForecast?.Date}");
            // output:
            //Date=8/1/2019 12:00:00 AM

            var sourceGenOptions = new JsonSerializerOptions
            {
                TypeInfoResolver = SourceGenerationContext.Default
            };
            weatherForecast = JsonSerializer.Deserialize(
                jsonString, typeof(WeatherForecast), sourceGenOptions)
                as WeatherForecast;
            Console.WriteLine($"Date={weatherForecast?.Date}");
            // output:
            //Date=8/1/2019 12:00:00 AM

            jsonString = JsonSerializer.Serialize(
                weatherForecast!, SourceGenerationContext.Default.WeatherForecast);
            Console.WriteLine(jsonString);
            // output:
            //{"Date":"2019-08-01T00:00:00","TemperatureCelsius":25,"Summary":"Hot"}

            jsonString = JsonSerializer.Serialize(
                weatherForecast, typeof(WeatherForecast), SourceGenerationContext.Default);
            Console.WriteLine(jsonString);
            // output:
            //{"Date":"2019-08-01T00:00:00","TemperatureCelsius":25,"Summary":"Hot"}

            sourceGenOptions = new JsonSerializerOptions
            {
                TypeInfoResolver = SourceGenerationContext.Default
            };

            jsonString = JsonSerializer.Serialize(
                weatherForecast, typeof(WeatherForecast), sourceGenOptions);
            Console.WriteLine(jsonString);
            // output:
            //{"Date":"2019-08-01T00:00:00","TemperatureCelsius":25,"Summary":"Hot"}
        }
    }
}

Specificare la modalità di generazione di origine

È possibile specificare la modalità basata su metadati o la modalità di ottimizzazione della serializzazione per un intero contesto, che può includere più tipi. In alternativa, è possibile specificare la modalità per un singolo tipo. In caso vengano eseguite entrambe, prevale la specifica della modalità per un tipo.

Esempio della modalità di Ottimizzazione della serializzazione (percorso rapido)

  • Per un intero contesto:

    [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Serialization)]
    [JsonSerializable(typeof(WeatherForecast))]
    internal partial class SerializeOnlyContext : JsonSerializerContext
    {
    }
    
  • Per un singolo tipo:

    [JsonSerializable(typeof(WeatherForecast), GenerationMode = JsonSourceGenerationMode.Serialization)]
    internal partial class SerializeOnlyWeatherForecastOnlyContext : JsonSerializerContext
    {
    }
    
  • Esempio di un programma completo

    using System.Text.Json;
    using System.Text.Json.Serialization;
    
    namespace SerializeOnlyNoOptions
    {
        public class WeatherForecast
        {
            public DateTime Date { get; set; }
            public int TemperatureCelsius { get; set; }
            public string? Summary { get; set; }
        }
    
        [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Serialization)]
        [JsonSerializable(typeof(WeatherForecast))]
        internal partial class SerializeOnlyContext : JsonSerializerContext
        {
        }
        
        [JsonSerializable(typeof(WeatherForecast), GenerationMode = JsonSourceGenerationMode.Serialization)]
        internal partial class SerializeOnlyWeatherForecastOnlyContext : JsonSerializerContext
        {
        }
    
         public class Program
        {
            public static void Main()
            {
                string jsonString;
                WeatherForecast weatherForecast = new()
                    { Date = DateTime.Parse("2019-08-01"), TemperatureCelsius = 25, Summary = "Hot" };
    
                // Use context that selects Serialization mode only for WeatherForecast.
                jsonString = JsonSerializer.Serialize(weatherForecast,
                    SerializeOnlyWeatherForecastOnlyContext.Default.WeatherForecast);
                Console.WriteLine(jsonString);
                // output:
                //{"Date":"2019-08-01T00:00:00","TemperatureCelsius":25,"Summary":"Hot"}
    
                // Use a context that selects Serialization mode.
                jsonString = JsonSerializer.Serialize(weatherForecast,
                    SerializeOnlyContext.Default.WeatherForecast);
                Console.WriteLine(jsonString);
                // output:
                //{"Date":"2019-08-01T00:00:00","TemperatureCelsius":25,"Summary":"Hot"}
            }
        }
    }
    

Esempio di modalità basata su metadati

  • Per un intero contesto:

    [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)]
    [JsonSerializable(typeof(WeatherForecast))]
    internal partial class MetadataOnlyContext : JsonSerializerContext
    {
    }
    
    jsonString = JsonSerializer.Serialize(
        weatherForecast!, MetadataOnlyContext.Default.WeatherForecast);
    
    weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(
        jsonString, MetadataOnlyContext.Default.WeatherForecast);
    
  • Per un singolo tipo:

    [JsonSerializable(typeof(WeatherForecast), GenerationMode = JsonSourceGenerationMode.Metadata)]
    internal partial class MetadataOnlyWeatherForecastOnlyContext : JsonSerializerContext
    {
    }
    
    jsonString = JsonSerializer.Serialize(
        weatherForecast!,
        MetadataOnlyWeatherForecastOnlyContext.Default.WeatherForecast);
    
    weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(
        jsonString, MetadataOnlyWeatherForecastOnlyContext.Default.WeatherForecast);
    
  • Esempio di un programma completo

    using System.Text.Json;
    using System.Text.Json.Serialization;
    
    namespace MetadataOnlyNoOptions
    {
        public class WeatherForecast
        {
            public DateTime Date { get; set; }
            public int TemperatureCelsius { get; set; }
            public string? Summary { get; set; }
        }
    
        [JsonSerializable(typeof(WeatherForecast), GenerationMode = JsonSourceGenerationMode.Metadata)]
        internal partial class MetadataOnlyWeatherForecastOnlyContext : JsonSerializerContext
        {
        }
    
        [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)]
        [JsonSerializable(typeof(WeatherForecast))]
        internal partial class MetadataOnlyContext : JsonSerializerContext
        {
        }
    
        public class Program
        {
            public static void Main()
            {
                string jsonString = """
                    {
                      "Date": "2019-08-01T00:00:00",
                      "TemperatureCelsius": 25,
                      "Summary": "Hot"
                    }
                    """;
                WeatherForecast? weatherForecast;
    
                // Deserialize with context that selects metadata mode only for WeatherForecast only.
                weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(
                    jsonString, MetadataOnlyWeatherForecastOnlyContext.Default.WeatherForecast);
                Console.WriteLine($"Date={weatherForecast?.Date}");
                // output:
                //Date=8/1/2019 12:00:00 AM
    
                // Serialize with context that selects metadata mode only for WeatherForecast only.
                jsonString = JsonSerializer.Serialize(
                    weatherForecast!,
                    MetadataOnlyWeatherForecastOnlyContext.Default.WeatherForecast);
                Console.WriteLine(jsonString);
                // output:
                //{"Date":"2019-08-01T00:00:00","TemperatureCelsius":25,"Summary":"Hot"}
    
                // Deserialize with context that selects metadata mode only.
                weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(
                    jsonString, MetadataOnlyContext.Default.WeatherForecast);
                Console.WriteLine($"Date={weatherForecast?.Date}");
                // output:
                //Date=8/1/2019 12:00:00 AM
    
                // Serialize with context that selects metadata mode only.
                jsonString = JsonSerializer.Serialize(
                    weatherForecast!, MetadataOnlyContext.Default.WeatherForecast);
                Console.WriteLine(jsonString);
                // output:
                //{"Date":"2019-08-01T00:00:00","TemperatureCelsius":0,"Summary":"Hot"}
            }
        }
    }
    

Supporto della generazione di origine in ASP.NET Core

Nelle app Blazor usare overload di metodi di estensione HttpClientJsonExtensions.GetFromJsonAsync e HttpClientJsonExtensions.PostAsJsonAsync che accettano un contesto di generazione di origine o TypeInfo<TValue>.

A partire da .NET 8, è anche possibile usare overload di metodi di estensione HttpClientJsonExtensions.GetFromJsonAsAsyncEnumerable che accettano un contesto di generazione di origine o TypeInfo<TValue>.

In Razor Pages, MVC, SignalR e nelle app per le API Web usare la proprietà JsonSerializerOptions.TypeInfoResolver per specificare il contesto.

[JsonSerializable(typeof(WeatherForecast[]))]
internal partial class MyJsonContext : JsonSerializerContext { }
var serializerOptions = new JsonSerializerOptions
{
    TypeInfoResolver = MyJsonContext.Default
};

services.AddControllers().AddJsonOptions(
    static options =>
        options.JsonSerializerOptions.TypeInfoResolverChain.Add(MyJsonContext.Default));

In Razor Pages, MVC, SignalR e nelle app per le API Web usare la proprietà JsonSerializerOptions.TypeInfoResolver per specificare il contesto.

[JsonSerializable(typeof(WeatherForecast[]))]
internal partial class MyJsonContext : JsonSerializerContext { }
var serializerOptions = new JsonSerializerOptions
{
    TypeInfoResolver = MyJsonContext.Default
};

services.AddControllers().AddJsonOptions(
    static options =>
        options.JsonSerializerOptions = serializerOptions);

In Razor Pages, MVC, SignalR e nelle app per le API Web usare il metodo AddContext di JsonSerializerOptions, come illustrato nell'esempio seguente:

[JsonSerializable(typeof(WeatherForecast[]))]
internal partial class MyJsonContext : JsonSerializerContext { }
services.AddControllers().AddJsonOptions(options =>
    options.JsonSerializerOptions.AddContext<MyJsonContext>());

Nota

JsonSourceGenerationMode.Serialization o la serializzazione a percorso rapido, non è supportata per la serializzazione asincrona.

In .NET 7 e versioni precedenti, questa limitazione si applica anche agli overload sincroni di JsonSerializer.Serialize che accettano un Stream. A partire da .NET 8, anche se la serializzazione in streaming richiede modelli basati sui metadati, verrà eseguito il fallback al percorso rapido se i payload sono noti per essere sufficientemente piccoli da adattarsi alle dimensioni predeterminate del buffer. Per ulteriori informazioni, vedere https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-8/#json.

Disabilitare le impostazioni predefinite di reflection

Poiché System.Text.Json usa la reflection per impostazione predefinita, la chiamata a un metodo di serializzazione di base può interrompere le app native AOT, che non supporta tutte le API di reflection necessarie. Queste interruzioni possono essere difficili da diagnosticare perché possono essere imprevedibili e le app vengono spesso sottoposte a debug usando il runtime CoreCLR, in cui funziona la reflection. Se invece viene la serializzazione basata su reflection disabilita in modo esplicito, le interruzioni sono più facili da diagnosticare. Il codice che usa la serializzazione basata su reflection causerà la generazione di un InvalidOperationException con un messaggio descrittivo in fase di esecuzione.

Per disabilitare la reflection predefinita nell'app, impostare la proprietà MSBuild JsonSerializerIsReflectionEnabledByDefault su false nel file di progetto:

<PropertyGroup>
  <JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
</PropertyGroup>
  • Il comportamento di questa proprietà è coerente indipendentemente dal runtime, CoreCLR o Native AOT.
  • Se non si specifica questa proprietà e PublishTrimmed è abilitato, la serializzazione basata su reflection viene disabilitata automaticamente.

È possibile controllare a livello di codice se la reflection è disabilitata tramite la proprietà JsonSerializer.IsReflectionEnabledByDefault. Il seguente frammento di codice mostra come si potrebbe configurare il serializzatore, in base all'abilitazione della reflection:

static JsonSerializerOptions CreateDefaultOptions()
{
    return new()
    {
        TypeInfoResolver = JsonSerializer.IsReflectionEnabledByDefault
            ? new DefaultJsonTypeInfoResolver()
            : MyContext.Default
    };
}

Poiché la proprietà viene considerata come costante del tempo di collegamento, il metodo precedente non blocca il risolutore basato su reflection nelle applicazioni eseguite in AOT nativo.

Specificare le opzioni

In .NET 8 e versioni successive, la maggior parte delle opzioni che è possibile impostare usando JsonSerializerOptions può essere impostata anche usando l'attributo JsonSourceGenerationOptionsAttribute. Il vantaggio di impostare le opzioni tramite l'attributo è che la configurazione viene specificata in fase di compilazione, il che garantisce che la proprietà generata MyContext.Default sia preconfigurata con tutte le opzioni pertinenti impostate.

Nel codice seguente viene illustrato come impostare le opzioni usando l'attributo JsonSourceGenerationOptionsAttribute.

[JsonSourceGenerationOptions(
    WriteIndented = true,
    PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
    GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(WeatherForecast))]
internal partial class SerializationModeOptionsContext : JsonSerializerContext
{
}

Quando si usa JsonSourceGenerationOptionsAttribute per specificare le opzioni di serializzazione, chiamare uno dei metodi di serializzazione seguenti:

  • Metodo JsonSerializer.Serialize che accetta un TypeInfo<TValue>. Passarlo alla proprietà Default.<TypeName> della classe del contesto:

    jsonString = JsonSerializer.Serialize(
        weatherForecast, SerializationModeOptionsContext.Default.WeatherForecast);
    
  • Metodo JsonSerializer.Serialize che accetta un contesto. Passarlo alla proprietà statica Default della classe del contesto.

    jsonString = JsonSerializer.Serialize(
        weatherForecast, typeof(WeatherForecast), SerializationModeOptionsContext.Default);
    

Se viene chiamato un metodo che consente di passare la propria istanza di Utf8JsonWriter, l'impostazione dello scrittore Indented viene rispettata invece dell'opzione JsonSourceGenerationOptionsAttribute.WriteIndented.

Se si crea e si usa un'istanza di contesto chiamando il costruttore che accetta un'istanza di JsonSerializerOptions, l'istanza fornita verrà usata al posto delle opzioni specificate da JsonSourceGenerationOptionsAttribute.

Ecco gli esempi precedenti in un programma completo:

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

namespace SerializeOnlyWithOptions
{
    public class WeatherForecast
    {
        public DateTime Date { get; set; }
        public int TemperatureCelsius { get; set; }
        public string? Summary { get; set; }
    }

    [JsonSourceGenerationOptions(
        WriteIndented = true,
        PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
        GenerationMode = JsonSourceGenerationMode.Serialization)]
    [JsonSerializable(typeof(WeatherForecast))]
    internal partial class SerializationModeOptionsContext : JsonSerializerContext
    {
    }
    public class Program
    {
        public static void Main()
        {
            string jsonString;
            WeatherForecast weatherForecast = new()
                { Date = DateTime.Parse("2019-08-01"), TemperatureCelsius = 25, Summary = "Hot" };

            // Serialize using TypeInfo<TValue> provided by the context
            // and options specified by [JsonSourceGenerationOptions].
            jsonString = JsonSerializer.Serialize(
                weatherForecast, SerializationModeOptionsContext.Default.WeatherForecast);
            Console.WriteLine(jsonString);
            // output:
            //{
            //  "date": "2019-08-01T00:00:00",
            //  "temperatureCelsius": 0,
            //  "summary": "Hot"
            //}

            // Serialize using Default context
            // and options specified by [JsonSourceGenerationOptions].
            jsonString = JsonSerializer.Serialize(
                weatherForecast, typeof(WeatherForecast), SerializationModeOptionsContext.Default);
            Console.WriteLine(jsonString);
            // output:
            //{
            //  "date": "2019-08-01T00:00:00",
            //  "temperatureCelsius": 0,
            //  "summary": "Hot"
            //}
        }
    }
}

Specificare le opzioni usando JsonSerializerOptions

Alcune opzioni di JsonSerializerOptions non possono essere impostate usando JsonSourceGenerationOptionsAttribute. Per specificare le opzioni tramite JsonSerializerOptions:

  • Creare un'istanza di JsonSerializerOptions.
  • Creare un'istanza della classe che deriva da JsonSerializerContext e passare l'istanza di JsonSerializerOptions al costruttore.
  • Chiamare metodi di serializzazione o deserializzazione di JsonSerializer che accettano un'istanza di contesto o TypeInfo<TValue>.

Ecco l’esempio di una classe di contesto seguita dal codice di esempio di serializzazione e deserializzazione:

[JsonSerializable(typeof(WeatherForecast))]
internal partial class OptionsExampleContext : JsonSerializerContext
{
}
jsonString = JsonSerializer.Serialize(
    weatherForecast,
    typeof(WeatherForecast),
    new OptionsExampleContext(
        new JsonSerializerOptions(JsonSerializerDefaults.Web)));
weatherForecast = JsonSerializer.Deserialize(
    jsonString, 
    typeof(WeatherForecast), 
    new OptionsExampleContext(
        new JsonSerializerOptions(JsonSerializerDefaults.Web)))
        as WeatherForecast;

Ecco gli esempi precedenti in un programma completo:

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

namespace JsonSerializerOptionsExample
{
    public class WeatherForecast
    {
        public DateTime Date { get; set; }
        public int TemperatureCelsius { get; set; }
        public string? Summary { get; set; }
    }

    [JsonSerializable(typeof(WeatherForecast))]
    internal partial class OptionsExampleContext : JsonSerializerContext
    {
    }

    public class Program
    {
        public static void Main()
        {
            string jsonString = """
                {
                  "date": "2019-08-01T00:00:00",
                  "temperatureCelsius": 25,
                  "summary": "Hot"
                }
                """;
            WeatherForecast? weatherForecast;

            weatherForecast = JsonSerializer.Deserialize(
                jsonString, 
                typeof(WeatherForecast), 
                new OptionsExampleContext(
                    new JsonSerializerOptions(JsonSerializerDefaults.Web)))
                    as WeatherForecast;
            Console.WriteLine($"Date={weatherForecast?.Date}");
            // output:
            //Date=8/1/2019 12:00:00 AM

            jsonString = JsonSerializer.Serialize(
                weatherForecast,
                typeof(WeatherForecast),
                new OptionsExampleContext(
                    new JsonSerializerOptions(JsonSerializerDefaults.Web)));
            Console.WriteLine(jsonString);
            // output:
            //{ "date":"2019-08-01T00:00:00","temperatureCelsius":25,"summary":"Hot"}
        }
    }
}

Combinare i generatori di origine

È possibile combinare contratti da più contesti generati dall'origine all'interno di una singola istanza di JsonSerializerOptions. Utilizzare la proprietà JsonSerializerOptions.TypeInfoResolver per concatenare più contesti combinati usando il metodo JsonTypeInfoResolver.Combine(IJsonTypeInfoResolver[]).

var options = new JsonSerializerOptions
{
    TypeInfoResolver = JsonTypeInfoResolver.Combine(ContextA.Default, ContextB.Default, ContextC.Default);
};

A partire da .NET 8, se in un secondo momento si vuole anteporre o aggiungere un altro contesto, è possibile farlo usando la proprietà JsonSerializerOptions.TypeInfoResolverChain. L'ordinamento della catena è significativo: JsonSerializerOptions esegue una query su ognuno dei risolutori nell'ordine specificato e restituisce il primo risultato non Null.

options.TypeInfoResolverChain.Add(ContextD.Default); // Append to the end of the list.
options.TypeInfoResolverChain.Insert(0, ContextE.Default); // Insert at the beginning of the list.

Qualsiasi modifica apportata alla proprietà TypeInfoResolverChain viene riflessa da TypeInfoResolver e viceversa.

Serializzare i campi di enumerazione come stringhe

Le enumerazioni vengono serializzate come numeri per impostazione predefinita. Per serializzare i campi di un'enumerazione specifica come stringhe quando si usa la generazione di origine, annotarlo con il convertitore JsonStringEnumConverter<TEnum>. In alternativa, per impostare un criterio generale per tutte le enumerazioni, usare l'attributo JsonSourceGenerationOptionsAttribute.

Convertitore JsonStringEnumConverter<T>

Per serializzare i nomi delle enumerazioni come stringhe usando la generazione di origine, usare il convertitore JsonStringEnumConverter<TEnum>. (Il tipo non generico JsonStringEnumConverter non è supportato dal runtime AOT nativo).

Annotare il tipo di enumerazione con il convertitore JsonStringEnumConverter<TEnum> usando l'attributo JsonConverterAttribute:

public class WeatherForecastWithPrecipEnum
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public Precipitation? Precipitation { get; set; }
}

[JsonConverter(typeof(JsonStringEnumConverter<Precipitation>))]
public enum Precipitation
{
    Drizzle, Rain, Sleet, Hail, Snow
}

Creare una classe JsonSerializerContext e annotarla con il JsonSerializableAttribute:

[JsonSerializable(typeof(WeatherForecastWithPrecipEnum))]
public partial class Context1 : JsonSerializerContext { }

Il codice seguente serializza i nomi delle enumerazioni anziché i valori numerici:

var weatherForecast = new WeatherForecastWithPrecipEnum
{
    Date = DateTime.Parse("2019-08-01"),
    TemperatureCelsius = 25,
    Precipitation = Precipitation.Sleet
};

var options = new JsonSerializerOptions
{
    WriteIndented = true,
    TypeInfoResolver = Context1.Default,
};
string? jsonString = JsonSerializer.Serialize(weatherForecast, options);

Il codice JSON risultante è simile al seguente esempio:

{
  "Date": "2019-08-01T00:00:00-07:00",
  "TemperatureCelsius": 25,
  "Precipitation": "Sleet"
}

Criteri di copertura

Anziché usare il tipo JsonStringEnumConverter<TEnum>, è possibile applicare un criterio di coperta per serializzare le enumerazioni come stringhe usando JsonSourceGenerationOptionsAttribute. Creare una classe JsonSerializerContext e annotarla con gli attributi JsonSerializableAttribute e JsonSourceGenerationOptionsAttribute:

[JsonSourceGenerationOptions(UseStringEnumConverter = true)]
[JsonSerializable(typeof(WeatherForecast2WithPrecipEnum))]
public partial class Context2 : JsonSerializerContext { }

Si noti che l'enumerazione non ha JsonConverterAttribute:

public class WeatherForecast2WithPrecipEnum
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public Precipitation2? Precipitation { get; set; }
}

public enum Precipitation2
{
    Drizzle, Rain, Sleet, Hail, Snow
}

Vedi anche