Personalizar o comportamento de desserialização
O JsonSerializer.Deserialize
método é usado para converter uma cadeia de caracteres JSON (JavaScript Object Notation) de volta em um objeto C#. A desserialização é usada ao ler arquivos JSON, receber dados JSON de APIs ou processamento geral de dados JSON. O Deserialize
método usa uma cadeia de caracteres JSON e o tipo do objeto a ser criado como entrada e retorna uma instância desse objeto.
O código a seguir demonstra como converter uma cadeia de caracteres JSON de volta em um objeto usando JsonSerializer.Deserialize
:
string jsonString = """{"Name":"Anette Thomsen","Age":30,"Address":"123 Main St"}""";
var customer = JsonSerializer.Deserialize<Employee>(jsonString);
Comportamento de desserialização
Assim como a serialização, a desserialização segue alguns comportamentos padrão. Os seguintes comportamentos se aplicam ao desserializar o JSON:
- Por padrão, a correspondência de nome de propriedade diferencia maiúsculas de minúsculas. Você pode especificar a insensibilidade de maiúsculas de minúsculas.
- Construtores não públicos são ignorados pelo serializador.
- A desserialização para objetos ou propriedades imutáveis que não têm acessadores de conjunto público tem suporte, mas não está habilitada por padrão. Consulte os tipos e registros imutáveis.
- Por padrão, há suporte para enumerações como números. Você pode desserializar campos de enumeração de cadeia de caracteres.
- Por padrão, os campos são ignorados. Você pode incluir campos.
- Por padrão, comentários ou vírgulas à direita no JSON geram exceções. Você pode permitir comentários e vírgulas à direita.
- A profundidade máxima padrão é 64.
Personalizar o comportamento de desserialização usando JsonSerializerOptions
A JsonSerializerOptions
classe permite personalizar o comportamento de desserialização do JsonSerializer.Deerialize
método.
As seguintes propriedades são demonstradas durante esta unidade:
-
RespectRequiredConstructorParameters
: essa propriedade especifica se todos os parâmetros de construtor não opcionais são necessários para desserialização JSON. -
PreferredObjectCreationHandling
: essa propriedade especifica o comportamento de tratamento de criação de objeto preferencial durante a desserialização.
Outras propriedades que normalmente são usadas para personalizar a desserialização incluem:
-
Converters
: essa propriedade permite que você adicione conversores personalizados para lidar com tipos específicos ou objetos complexos. Por exemplo, você pode criar um conversor personalizado para uma classe que contém objetos aninhados ou propriedades complexas. -
DefaultBufferSize
: essa propriedade define o tamanho do buffer padrão em bytes, o que pode melhorar o desempenho ao desserializar cadeias de caracteres JSON grandes ou matrizes de bytes UTF-8. -
AllowTrailingCommas
: essa propriedade permite vírgulas à direita em matrizes JSON e objetos, o que pode ser útil ao lidar com dados JSON que podem ter vírgulas à direita. -
AllowOutOfOrderMetadataProperties
: essa propriedade remove o requisito de que as propriedades de metadados JSON, como$id
e$type
, sejam especificadas no início do objeto JSON desserializado. -
NumberHandling
: essa propriedade especifica como os tipos de número devem ser tratados ao serializar ou desserializar, o que pode ser útil para garantir uma representação precisa de valores numéricos.
Desserializar as propriedades necessárias
Você pode marcar determinadas propriedades para significar que elas devem estar presentes no conteúdo JSON para que a desserialização seja bem-sucedida. Da mesma forma, você pode definir uma opção para especificar que todos os parâmetros de construtor não opcionais estão presentes no conteúdo JSON. Se uma ou mais dessas propriedades necessárias não estiverem presentes, os JsonSerializer.Deserialize
métodos gerarão um JsonException
.
Há três maneiras de marcar uma propriedade ou campo conforme necessário para desserialização JSON:
- Adicionando o
required
modificador. - Anotando com
JsonRequiredAttribute
. - Modificando a
JsonPropertyInfo.IsRequired
propriedade do modelo de contrato.
Para especificar que todos os parâmetros de construtor não opcionais são necessários para desserialização JSON, defina a opção JsonSerializerOptions.RespectRequiredConstructorParameters
como true
.
Da perspectiva do serializador, o modificador e [JsonRequired]
o atributo C# required
são equivalentes e ambos são mapeados para a mesma parte de metadados, que é JsonPropertyInfo.IsRequired
. Na maioria dos casos, você usaria a palavra-chave C# interna. No entanto, nos seguintes casos, você deve usar JsonRequiredAttribute
em vez disso:
- Se você estiver usando uma linguagem de programação diferente de C# ou uma versão de nível inferior do C#.
- Se você quiser apenas o requisito para aplicar à desserialização JSON.
- Se você estiver usando
System.Text.Json
a serialização no modo de geração de origem. Nesse caso, seu código não será compilado se você usar o modificador necessário, pois a geração de origem ocorre no momento da compilação.
O snippet de código a seguir mostra um exemplo de uma propriedade modificada com a required
palavra-chave. Essa propriedade deve estar presente no conteúdo JSON para que a desserialização seja bem-sucedida.
public static void RunIt()
{
// The following line throws a JsonException at run time.
Console.WriteLine(JsonSerializer.Deserialize<Person>("""{"Age": 42}"""));
}
public class Person
{
public required string Name { get; set; }
public int Age { get; set; }
}
Como alternativa, você pode usar JsonRequiredAttribute
:
public static void RunIt()
{
// The following line throws a JsonException at run time.
Console.WriteLine(JsonSerializer.Deserialize<Person>("""{"Age": 42}"""));
}
public class Person
{
[JsonRequired]
public string Name { get; set; }
public int Age { get; set; }
}
Também é possível controlar se uma propriedade é necessária por meio do modelo de contrato usando a JsonPropertyInfo.IsRequired
propriedade:
public static void RunIt()
{
var options = new JsonSerializerOptions
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver
{
Modifiers =
{
static typeInfo =>
{
if (typeInfo.Kind != JsonTypeInfoKind.Object)
return;
foreach (JsonPropertyInfo propertyInfo in typeInfo.Properties)
{
// Strip IsRequired constraint from every property.
propertyInfo.IsRequired = false;
}
}
}
}
};
// Deserialization succeeds even though
// the Name property isn't in the JSON payload.
JsonSerializer.Deserialize<Person>("""{"Age": 42}""", options);
}
public class Person
{
public required string Name { get; set; }
public int Age { get; set; }
}
Desserializar e preencher propriedades inicializadas
Ao desserializar uma cadeia de caracteres JSON em um objeto, você pode especificar se determinadas propriedades são inicializadas ou não. Isso é útil para garantir que o objeto desserializado tenha todos os dados necessários antes que ele possa ser usado.
A partir do .NET 8, você pode especificar uma preferência para substituir ou preencher as propriedades do .NET quando o JSON for desserializado. A JsonObjectCreationHandling
enumeração fornece as opções de tratamento de criação de objeto:
-
JsonObjectCreationHandling.Replace
: o comportamento padrão. As propriedades são substituídas pelos valores do conteúdo JSON. Esse é o mesmo comportamento das versões anteriores do .NET. -
JsonObjectCreationHandling.Populate
: as propriedades são preenchidas com os valores da carga JSON. Isso significa que, se uma propriedade já estiver inicializada, seu valor será atualizado com o valor da carga JSON.
Comportamento padrão (substituir)
O System.Text.Json
desserializador sempre cria uma nova instância do tipo de destino. No entanto, mesmo que uma nova instância seja criada, algumas propriedades e campos já podem ser inicializados como parte da construção do objeto. Considere o seguinte tipo:
class A
{
public List<int> Numbers1 { get; } = [1, 2, 3];
public List<int> Numbers2 { get; set; } = [1, 2, 3];
}
Quando você cria uma instância dessa classe, as duas List
propriedades de tipo são inicializadas com três elementos (1, 2 e 3). Se você desserializar JSON para esse tipo, o comportamento padrão será substituir os valores de propriedade por dados do conteúdo JSON.
- Pois
Numbers1
, como é somente leitura (sem setter), ele ainda tem os valores 1, 2 e 3 em sua lista. - Para
Numbers2
, que é leitura-gravação, uma nova lista é alocada e os valores do JSON são adicionados.
Por exemplo, se você executar o código de desserialização a seguir, Numbers1
conterá os valores 1, 2 e 3 e Numbers2
conterá os valores 4, 5 e 6.
var json = """{"Numbers1": [4, 5, 6], "Numbers2": [4, 5, 6]}""";
var a = JsonSerializer.Deserialize<A>(json);
Console.WriteLine(string.Join(", ", a.Numbers1));
Console.WriteLine(string.Join(", ", a.Numbers2));
// Output: 1, 2, 3
// Output: 4, 5, 6
Comportamento de população
Você pode alterar o comportamento de desserialização para modificar (popular) propriedades e campos em vez de substituí-los:
- Para uma propriedade de tipo de coleção, o objeto é reutilizado sem limpar. Se a coleção for pré-preenchida com elementos, elas serão mostradas no resultado desserializado final juntamente com os valores do JSON.
- Para uma propriedade que é um objeto com propriedades, suas propriedades mutáveis são atualizadas para os valores JSON, mas a referência de objeto em si não é alterada.
- Para uma propriedade de tipo de struct, o comportamento efetivo é que, para suas propriedades mutáveis, todos os valores existentes são mantidos e novos valores do JSON são adicionados. No entanto, ao contrário de uma propriedade de referência, o objeto em si não é reutilizado, pois é um tipo de valor. Em vez disso, uma cópia do struct é modificada e reatribuída à propriedade.
Uma struct
propriedade deve ter um setter; caso contrário, uma InvalidOperationException
é lançada em tempo de execução.
Importante
O Populate
comportamento atualmente não funciona para tipos que têm um construtor parametrizado.
Como especificar
Há várias maneiras de especificar uma preferência para replace
ou populate
:
Use o
JsonObjectCreationHandlingAttribute
atributo para anotar no nível do tipo ou da propriedade. Se você definir o atributo no nível do tipo e definir suaHandling
propriedade comoPopulate
, o comportamento só se aplicará às propriedades em que a população é possível (por exemplo, os tipos de valor devem ter um setter).Se quiser que a preferência de todo o tipo seja
Populate
, mas quiser excluir uma ou mais propriedades desse comportamento, você poderá adicionar o atributo no nível do tipo e novamente no nível da propriedade para substituir o comportamento herdado. Esse padrão é mostrado no código a seguir.// Type-level preference is Populate. [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)] class B { // For this property only, use Replace behavior. [JsonObjectCreationHandling(JsonObjectCreationHandling.Replace)] public List<int> Numbers1 { get; } = [1, 2, 3]; public List<int> Numbers2 { get; set; } = [1, 2, 3]; }
Defina
JsonSerializerOptions.PreferredObjectCreationHandling
para especificar uma preferência global.var options = new JsonSerializerOptions { PreferredObjectCreationHandling = JsonObjectCreationHandling.Populate };
Lendo arquivos JSON
Como ler arquivos JSON usando File.ReadAllText
.
using System;
using System.IO;
using System.Text.Json;
class Program
{
static void Main()
{
string jsonString = File.ReadAllText("customer.json");
var customer = JsonSerializer.Deserialize<BankCustomer>(jsonString);
Console.WriteLine($"Name: {customer.Name}, Age: {customer.Age}, Address: {customer.Address}");
}
}
Resumo
Nesta unidade, você aprendeu a personalizar o comportamento de desserialização padrão ao desserializar dados JSON em objetos C# usando o JsonSerializer.Deserialize
método. Você aprendeu sobre os comportamentos padrão, como marcar as propriedades conforme necessário e como especificar se as propriedades devem ser substituídas ou preenchidas durante a desserialização. Por fim, você aprendeu a ler arquivos JSON usando File.ReadAllText
.