Compartilhar via


Como usar a geração de origem em System.Text.Json

Este artigo mostra como usar a serialização com suporte à geração de código-fonte em seus aplicativos.

Para obter informações sobre os diferentes modos de geração de origem, confira os Modos de geração de origem.

Usar padrões de geração de origem

Para usar a geração de origem com todos os padrões (ambos os modos, opções padrão):

  1. Crie uma classe parcial que deriva de JsonSerializerContext.

  2. Especifique o tipo para serializar ou desserializar aplicando JsonSerializableAttribute à classe de contexto.

  3. Chame um método JsonSerializer que:

Por padrão, ambos os modos de geração de origem (otimização baseada em metadados e serialização) são usados se você não especificar um. Para obter informações sobre como especificar o modo a ser usado, consulte Especificar modo de geração de origem posteriormente neste artigo.

Este é o tipo usado nos seguintes exemplos:

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

Aqui está a classe de contexto configurada para gerar a origem para a classe anterior WeatherForecast :

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

Os tipos de membros WeatherForecast não precisam ser especificados explicitamente com atributos [JsonSerializable]. Membros declarados como object são uma exceção a esta regra. O tipo de runtime para um membro declarado como object precisa ser especificado. Por exemplo, suponha que você tenha a seguinte classe:

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

E você sabe que, em runtime, ele pode ter boolean e int objetos:

WeatherForecast wf = new() { Data = true, DataList = [true, 1] };

Então boolean e int devem ser declarados como [JsonSerializable]:

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

Para especificar a geração de origem para uma coleção, use [JsonSerializable] com o tipo de coleção. Por exemplo: [JsonSerializable(typeof(List<WeatherForecast>))].

JsonSerializer métodos que usam geração de código-fonte

Nos exemplos a seguir, a propriedade estática Default do tipo de contexto fornece uma instância do tipo de contexto com opções padrão. A instância de contexto fornece uma propriedade WeatherForecast que retorna uma instância JsonTypeInfo<WeatherForecast>. Você pode especificar um nome diferente para esta propriedade usando a propriedade TypeInfoPropertyName do atributo [JsonSerializable].

Exemplos de serialização

Usando JsonTypeInfo<T>:

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

Usando JsonSerializerContext:

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

Usando JsonSerializerOptions:

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

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

Exemplos de desserialização

Usando JsonTypeInfo<T>:

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

Usando JsonSerializerContext:

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

Usando JsonSerializerOptions:

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

Exemplo de programa completo

Aqui estão os exemplos anteriores em um programa 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(
                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<WeatherForecast>(jsonString, sourceGenOptions);
            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>(weatherForecast, sourceGenOptions);
            Console.WriteLine(jsonString);
            // output:
            //{"Date":"2019-08-01T00:00:00","TemperatureCelsius":25,"Summary":"Hot"}
        }
    }
}

Especifique o modo de geração de origem

Você pode especificar modo baseado em metadados ou modo de serialização-otimização para um contexto inteiro, que pode incluir vários tipos. Ou você pode especificar o modo para um tipo individual. Se você fizer as duas coisas, a especificação de modo para um tipo prevalece.

Exemplo do modo serialização-otimização (caminho rápido)

  • Para um contexto inteiro:

    [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Serialization)]
    [JsonSerializable(typeof(WeatherForecast))]
    internal partial class SerializeOnlyContext : JsonSerializerContext
    {
    }
    
  • Para um tipo individual:

    [JsonSerializable(typeof(WeatherForecast), GenerationMode = JsonSourceGenerationMode.Serialization)]
    internal partial class SerializeOnlyWeatherForecastOnlyContext : JsonSerializerContext
    {
    }
    
  • Exemplo de programa 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"}
            }
        }
    }
    

Exemplo do modo baseado em metadados

  • Para um contexto inteiro:

    [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);
    
  • Para um tipo individual:

    [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);
    
  • Exemplo de programa 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":25,"Summary":"Hot"}
            }
        }
    }
    

Suporte à geração de origem no ASP.NET Core

Em aplicativos Blazor, use sobrecargas e métodos de extensão HttpClientJsonExtensions.GetFromJsonAsync e HttpClientJsonExtensions.PostAsJsonAsync que usam um contexto de geração de origem ou TypeInfo<TValue>.

A partir do .NET 8, você também pode usar sobrecargas de métodos de extensão HttpClientJsonExtensions.GetFromJsonAsAsyncEnumerable que aceitam um contexto de geração de origem ou TypeInfo<TValue>.

Em aplicativos do Razor Pages, MVC, SignalR e de API Web, use a propriedade JsonSerializerOptions.TypeInfoResolver para especificar o contexto.

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

Observação

JsonSourceGenerationMode.SerializationNão há suporte para serialização assíncrona ou serialização de caminho rápido . Embora a serialização de streaming exija modelos baseados em metadados, ela voltará ao caminho rápido se os conteúdos forem conhecidos por serem pequenos o suficiente para se ajustarem ao tamanho predeterminado do buffer. Para obter mais informações, consulte https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-8/#json.

Desabilitar padrões de reflexão

Como o System.Text.Json usa reflexão por padrão, chamar um método básico de serialização pode interromper aplicativos Native AOT, que não dão suporte a todas as APIs de reflexão necessárias. Essas interrupções podem ser desafiadoras para diagnosticar, pois podem ser imprevisíveis, e os aplicativos muitas vezes são depurados usando o runtime CoreCLR, onde a reflexão funciona. Em vez disso, se você desabilitar explicitamente a serialização baseada em reflexão, as interrupções serão mais fáceis de diagnosticar. O código que usa a serialização baseada em reflexão fará com que uma InvalidOperationException mensagem descritiva seja lançada em runtime.

Para desabilitar a reflexão padrão em seu aplicativo, defina a propriedade MSBuild JsonSerializerIsReflectionEnabledByDefault como false em seu arquivo de projeto:

<PropertyGroup>
  <JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
</PropertyGroup>
  • O comportamento dessa propriedade é consistente independentemente do runtime, seja CoreCLR ou Native AOT.
  • Se você não especificar essa propriedade e PublishTrimmed estiver habilitado, a serialização baseada em reflexão será desabilitada automaticamente.

Você pode verificar programaticamente se a reflexão está desabilitada usando a propriedade JsonSerializer.IsReflectionEnabledByDefault. O snippet de código a seguir mostra como você pode configurar seu serializador dependendo se a reflexão está habilitada:

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

Como a propriedade é tratada como uma constante de tempo de link, o método anterior não enraíza o resolvedor baseado em reflexão em aplicativos executados no Native AOT.

Especificar opções

No .NET 8 e versões posteriores, a maioria das opções que você pode definir usando JsonSerializerOptions também pode ser definida usando o atributo JsonSourceGenerationOptionsAttribute. A vantagem de definir opções por meio do atributo é que a configuração é especificada em tempo de compilação, garantindo que a propriedade MyContext.Default gerada seja pré-configurada com todas as opções relevantes definidas.

O código a seguir mostra como definir opções usando o atributo JsonSourceGenerationOptionsAttribute.

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

Ao usar JsonSourceGenerationOptionsAttribute para especificar opções de serialização, chame um dos seguintes métodos de serialização:

  • Um método JsonSerializer.Serialize que usa um TypeInfo<TValue>. Passe a propriedade Default.<TypeName> da classe de contexto:

    jsonString = JsonSerializer.Serialize(
        weatherForecast, SerializationModeOptionsContext.Default.WeatherForecast);
    
  • Um método JsonSerializer.Serialize que usa um contexto. Passe a propriedade estática Default de sua classe de contexto.

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

Se você chamar um método que permite passar em sua própria instância de Utf8JsonWriter, a configuração Indented do gravador é respeitada em vez da opção JsonSourceGenerationOptionsAttribute.WriteIndented.

Se você criar e usar uma instância de contexto chamando o construtor que usa uma instância JsonSerializerOptions, a instância fornecida será usada em vez das opções especificadas por JsonSourceGenerationOptionsAttribute.

O código a seguir mostra os exemplos anteriores em um programa 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"
            //}
        }
    }
}

Combinar geradores de origem

Você pode combinar contratos de vários contextos gerados pela origem dentro de uma única instância de JsonSerializerOptions. Use a propriedade JsonSerializerOptions.TypeInfoResolver para encadear vários contextos que foram combinados usando o método JsonTypeInfoResolver.Combine(IJsonTypeInfoResolver[]).

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

A partir do .NET 8, se você quiser preceder ou acrescentar outro contexto posteriormente, poderá fazê-lo usando a propriedade JsonSerializerOptions.TypeInfoResolverChain. A ordenação da cadeia é significativa: JsonSerializerOptions consulta cada um dos resolvedores em sua ordem especificada e retorna o primeiro resultado que não é nulo.

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.

Qualquer alteração feita na propriedade TypeInfoResolverChain é refletida por TypeInfoResolver e vice-versa.

Serializar campos de enumeração como cadeias de caracteres

Por padrão, as enumerações são serializadas como números. Para serializar os campos específicos de uma enumeração como cadeias de caracteres ao usar a geração de origem, anote-os com o conversor JsonStringEnumConverter<TEnum>. Ou para definir uma política global para todas as enumerações, use o atributo JsonSourceGenerationOptionsAttribute.

Conversor JsonStringEnumConverter<T>

Para serializar nomes de enumeração como cadeias de caracteres utilizando a geração de fonte, use o conversor JsonStringEnumConverter<TEnum>. (Não há suporte para o tipo JsonStringEnumConverter não genérico no runtime de AOT Nativa)

Anote o tipo de enumeração com o conversor JsonStringEnumConverter<TEnum> usando o atributo 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
}

Crie uma classe JsonSerializerContext e anote-a com o atributo JsonSerializableAttribute:

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

O código a seguir serializa os nomes de enumeração em vez dos valores numéricos:

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

O JSON resultante é semelhante ao seguinte exemplo:

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

Política ampla

Em vez de usar o tipo JsonStringEnumConverter<TEnum>, você poderá aplicar uma política ampla para serializar enumerações como cadeias de caracteres usando o JsonSourceGenerationOptionsAttribute. Crie uma classe JsonSerializerContext e anote-a com JsonSerializableAttributee JsonSourceGenerationOptionsAttribute atributos:

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

Observe que a enumeração não possui o 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
}

Nomes de membros de enumeração personalizados

A partir do .NET 9, você pode personalizar nomes de membros de enumeração usando o atributo JsonStringEnumMemberName attribute. Para obter mais informações, consulte Nomes de membros de enumeração personalizados.

Confira também