Como usar a geração de origem em System.Text.Json
A geração de fonte no System.Text.Json está disponível no .NET 6 e em versões posteriores. Quando usado em um aplicativo, a versão do idioma do aplicativo deve ser C# 9.0 ou posterior. Este artigo mostra como usar a serialização baseada na geração de origem 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):
Crie uma classe parcial que deriva de JsonSerializerContext.
Especifique o tipo para serializar ou desserializar aplicando JsonSerializableAttribute à classe de contexto.
Chame um método JsonSerializer que:
- Usa uma instância JsonTypeInfo<T> ou
- Usa uma instância JsonSerializerContext ou
- Usa uma instância JsonSerializerOptions e você definiu sua propriedade JsonSerializerOptions.TypeInfoResolver como a propriedade
Default
do tipo de contexto (somente .NET 7 e versão posterior).
Por padrão, ambos os modos de geração de origem 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.
Aqui está o tipo que é usado nos exemplos a seguir:
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 fonte para a classe WeatherForecast
anterior:
[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 pode ter objetos boolean
e int
:
WeatherForecast wf = new() { Data = true, DataList = new List<object> { 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 origem
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, typeof(WeatherForecast), sourceGenOptions);
Exemplos de desserialização
Usando JsonTypeInfo<T>:
weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(
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(
jsonString, typeof(WeatherForecast), sourceGenOptions)
as WeatherForecast;
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<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"}
}
}
}
Especifique o modo de geração de origem
Você pode especificar o modo baseado em metadados ou o modo de otimização de serializaçã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.
- Para um contexto inteiro, use a propriedade JsonSourceGenerationOptionsAttribute.GenerationMode.
- Para um tipo individual, use a propriedade JsonSerializableAttribute.GenerationMode.
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.Serialization ou serialização de caminho rápido, não tem suporte para serialização assíncrona.
No .NET 7 e versões anteriores, essa limitação também se aplica a sobrecargas síncronas de JsonSerializer.Serialize que aceitam um arquivo Stream. A partir do .NET 8, mesmo que 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 do buffer predeterminado. 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 serialização baseada em reflexão fará com que uma InvalidOperationException com uma mensagem descritiva seja gerada durante o tempo de execução.
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 umTypeInfo<TValue>
. Passe a propriedadeDefault.<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áticaDefault
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
.
Aqui estão 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 JsonSerializableAttribute e 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.