Dela via


Så här använder du källgenerering i System.Text.Json

Källgenerering i System.Text.Json är tillgängligt i .NET 6 och senare versioner. När den används i en app måste appens språkversion vara C# 9.0 eller senare. Den här artikeln visar hur du använder källgenereringsbaserad serialisering i dina appar.

Information om de olika källgenereringslägena finns i Källgenereringslägen.

Använd standardvärden för källgenerering

Om du vill använda källgenerering med alla standardvärden (båda lägena, standardalternativ):

  1. Skapa en partiell klass som härleds från JsonSerializerContext.

  2. Ange vilken typ som ska serialiseras eller deserialiseras genom att tillämpa JsonSerializableAttribute på kontextklassen.

  3. Anropa en JsonSerializer metod som antingen:

Som standard används båda källgenereringslägena om du inte anger något. Information om hur du anger vilket läge som ska användas finns i Ange källgenereringsläge senare i den här artikeln.

Här är den typ som används i följande exempel:

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

Här är kontextklassen som konfigurerats för källgenerering för föregående WeatherForecast klass:

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

Typerna av WeatherForecast medlemmar behöver inte uttryckligen anges med [JsonSerializable] attribut. Medlemmar som deklareras som object är ett undantag från den här regeln. Körningstypen för en medlem som deklarerats som object måste anges. Anta till exempel att du har följande klass:

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

Och du vet att den vid körning kan ha boolean och int objekt:

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

Sedan boolean och int måste deklareras som [JsonSerializable]:

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

Om du vill ange källgenerering för en samling använder du [JsonSerializable] med samlingstypen. Exempel: [JsonSerializable(typeof(List<WeatherForecast>))].

JsonSerializer metoder som använder källgenerering

I följande exempel ger den statiska Default egenskapen för kontexttypen en instans av kontexttypen med standardalternativ. Kontextinstansen tillhandahåller en WeatherForecast egenskap som returnerar en JsonTypeInfo<WeatherForecast> instans. Du kan ange ett annat namn för den här egenskapen med hjälp TypeInfoPropertyName av egenskapen för attributet [JsonSerializable] .

Serialiseringsexempel

Använda JsonTypeInfo<T>:

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

Använda JsonSerializerContext:

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

Använda JsonSerializerOptions:

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

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

Exempel på deserialisering

Använda JsonTypeInfo<T>:

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

Använda JsonSerializerContext:

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

Använda JsonSerializerOptions:

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

Exempel på slutfört program

Här är de föregående exemplen i ett fullständigt program:

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"}
        }
    }
}

Ange källgenereringsläge

Du kan ange metadatabaserat läge eller serialiseringsoptimeringsläge för en hel kontext, som kan innehålla flera typer. Eller så kan du ange läget för en enskild typ. Om du gör båda vinner lägesspecifikationen för en typ.

Exempel på serialiseringsoptimering (snabb sökväg)

  • För en hel kontext:

    [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Serialization)]
    [JsonSerializable(typeof(WeatherForecast))]
    internal partial class SerializeOnlyContext : JsonSerializerContext
    {
    }
    
  • För en enskild typ:

    [JsonSerializable(typeof(WeatherForecast), GenerationMode = JsonSourceGenerationMode.Serialization)]
    internal partial class SerializeOnlyWeatherForecastOnlyContext : JsonSerializerContext
    {
    }
    
  • Exempel på slutfört program

    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"}
            }
        }
    }
    

Exempel på metadatabaserat läge

  • För en hel kontext:

    [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);
    
  • För en enskild typ:

    [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);
    
  • Exempel på slutfört program

    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":25,"Summary":"Hot"}
            }
        }
    }
    

Stöd för källgenerering i ASP.NET Core

I Blazor-appar använder du överlagringar av HttpClientJsonExtensions.GetFromJsonAsync och HttpClientJsonExtensions.PostAsJsonAsync tilläggsmetoder som tar en källgenereringskontext eller TypeInfo<TValue>.

Från och med .NET 8 kan du också använda överlagringar av HttpClientJsonExtensions.GetFromJsonAsAsyncEnumerable tilläggsmetoder som accepterar en källgenereringskontext eller TypeInfo<TValue>.

I Appar för Razor Pages, MVC, SignalR och Web API använder du JsonSerializerOptions.TypeInfoResolver egenskapen för att ange kontexten.

[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));

Kommentar

JsonSourceGenerationMode.Serialization, eller snabbsökvägs serialisering, stöds inte för asynkron serialisering.

I .NET 7 och tidigare versioner gäller den här begränsningen även synkrona överlagringar av JsonSerializer.Serialize som accepterar en Stream. Från och med .NET 8, även om strömningsserialisering kräver metadatabaserade modeller, återgår den till snabb sökväg om nyttolasten är känd för att vara tillräckligt liten för att få plats i den förutbestämda buffertstorleken. Mer information finns i https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-8/#json.

Inaktivera reflektionsstandarder

Eftersom System.Text.Json använder reflektion som standard kan anrop av en grundläggande serialiseringsmetod bryta interna AOT-appar, som inte stöder alla nödvändiga reflektions-API:er. Dessa pauser kan vara svåra att diagnostisera eftersom de kan vara oförutsägbara och appar ofta debugged med hjälp av CoreCLR-körningen, där reflektion fungerar. Om du i stället uttryckligen inaktiverar reflektionsbaserad serialisering är det enklare att diagnostisera pauser. Kod som använder reflektionsbaserad serialisering gör att ett InvalidOperationException med ett beskrivande meddelande genereras vid körning.

Om du vill inaktivera standardreflektion i din app anger du JsonSerializerIsReflectionEnabledByDefault egenskapen MSBuild till false i projektfilen:

<PropertyGroup>
  <JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
</PropertyGroup>
  • Beteendet för den här egenskapen är konsekvent oavsett körning, antingen CoreCLR eller intern AOT.
  • Om du inte anger den här egenskapen och PublishTrimmed är aktiverat inaktiveras reflektionsbaserad serialisering automatiskt.

Du kan programmatiskt kontrollera om reflektion är inaktiverat med hjälp JsonSerializer.IsReflectionEnabledByDefault av egenskapen . Följande kodfragment visar hur du kan konfigurera serialiseraren beroende på om reflektion är aktiverat:

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

Eftersom egenskapen behandlas som en länktidskonstant rotar inte den tidigare metoden den reflektionsbaserade matcharen i program som körs i intern AOT.

Ange alternativ

I .NET 8 och senare versioner kan de flesta alternativ som du kan ange med hjälp JsonSerializerOptions av också anges med hjälp av JsonSourceGenerationOptionsAttribute attributet. Fördelen med att ange alternativ via attributet är att konfigurationen anges vid kompileringstid, vilket säkerställer att den genererade MyContext.Default egenskapen är förkonfigurerad med alla relevanta alternativ inställda.

Följande kod visar hur du anger alternativ med hjälp av attributet JsonSourceGenerationOptionsAttribute .

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

När du använder JsonSourceGenerationOptionsAttribute för att ange serialiseringsalternativ anropar du någon av följande serialiseringsmetoder:

  • En JsonSerializer.Serialize metod som tar en TypeInfo<TValue>. Skicka egenskapen för kontextklassen Default.<TypeName> :

    jsonString = JsonSerializer.Serialize(
        weatherForecast, SerializationModeOptionsContext.Default.WeatherForecast);
    
  • En JsonSerializer.Serialize metod som tar en kontext. Skicka den statiska Default egenskapen för kontextklassen.

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

Om du anropar en metod som låter dig skicka in din egen instans av Utf8JsonWriter, respekteras författarens Indented inställning i stället för alternativet JsonSourceGenerationOptionsAttribute.WriteIndented .

Om du skapar och använder en kontextinstans genom att anropa konstruktorn som tar en JsonSerializerOptions instans används den angivna instansen i stället för de alternativ som anges av JsonSourceGenerationOptionsAttribute.

Här är de föregående exemplen i ett fullständigt program:

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"
            //}
        }
    }
}

Kombinera källgeneratorer

Du kan kombinera kontrakt från flera källgenererade kontexter i en enda JsonSerializerOptions instans. Använd egenskapen JsonSerializerOptions.TypeInfoResolver för att länka flera kontexter som har kombinerats med hjälp JsonTypeInfoResolver.Combine(IJsonTypeInfoResolver[]) av metoden.

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

Från och med .NET 8 kan du göra det med hjälp av JsonSerializerOptions.TypeInfoResolverChain egenskapen om du senare vill förbereda eller lägga till en annan kontext. Ordningen på kedjan är viktig: JsonSerializerOptions frågar var och en av matcharna i sin angivna ordning och returnerar det första resultatet som inte är 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.

Alla ändringar som görs i egenskapen TypeInfoResolverChain återspeglas av TypeInfoResolver och vice versa.

Serialisera uppräkningsfält som strängar

Som standard serialiseras uppräkningar som tal. Om du vill serialisera ett visst uppräkningsfält som strängar när du använder källgenerering kommenterar du det med JsonStringEnumConverter<TEnum> konverteraren. Om du vill ange en avropsprincip för alla uppräkningar använder du attributet JsonSourceGenerationOptionsAttribute .

JsonStringEnumConverter<T> omvandlare

Om du vill serialisera uppräkningsnamn som strängar med källgenerering använder du JsonStringEnumConverter<TEnum> konverteraren. (Den icke-generiska JsonStringEnumConverter typen stöds inte av den interna AOT-körningen.)

Kommentera uppräkningstypen med JsonStringEnumConverter<TEnum> konverteraren med hjälp av JsonConverterAttribute attributet:

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
}

Skapa en JsonSerializerContext klass och kommentera den med attributet JsonSerializableAttribute :

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

Följande kod serialiserar uppräkningsnamnen i stället för de numeriska värdena:

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);

Den resulterande JSON:en ser ut som i följande exempel:

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

Övergripande princip

I stället för att JsonStringEnumConverter<TEnum> använda typen kan du använda en generell princip för att serialisera uppräkningar som strängar med hjälp JsonSourceGenerationOptionsAttributeav . Skapa en JsonSerializerContext klass och kommentera den med attributen JsonSerializableAttribute och JsonSourceGenerationOptionsAttribute :

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

Observera att uppräkningen inte har 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
}

Anpassade uppräkningsmedlemsnamn

Från och med .NET 9 kan du anpassa uppräkningsmedlemsnamn med hjälp av attributet JsonStringEnumMemberName. Mer information finns i Anpassade uppräkningsmedlemsnamn.

Se även