Preencher propriedades inicializadas
A partir do .NET 8, você pode especificar uma preferência para substituir ou preencher propriedades do .NET quando JSON for desserializado. A enumeração JsonObjectCreationHandling fornece as opções de tratamento de criação de objeto:
Comportamento padrão (substituir)
O desserializador System.Text.Json 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, o valor da propriedade Numbers1
(e Numbers2
) é uma lista com três elementos (1, 2 e 3). Se você desserializar o JSON para esse tipo, o comportamento de padrão será que os valores de propriedade serão substituídos:
- Para
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.
A? a = JsonSerializer.Deserialize<A>("""{"Numbers1": [4,5,6], "Numbers2": [4,5,6]}""");
Comportamento de preenchimento
A partir do .NET 8, você pode alterar o comportamento de desserialização para modificar (preencher) 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, eles serão exibidos no resultado final desserializado junto com os valores do JSON. Para obter um exemplo, consulte Exemplo de propriedade de coleção.
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 do tipo struct, o comportamento efetivo é que, para suas propriedades mutáveis, quaisquer 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 de struct é modificada e, em seguida, reatribuída à propriedade. Para obter um exemplo, consulte Exemplo de propriedade struct.
Uma propriedade struct deve ter um setter; caso contrário, um InvalidOperationException é lançado em tempo de execução.
Observação
Atualmente, o comportamento de preenchimento não funciona para tipos que têm um construtor parametrizado. Para obter mais informações, confira problema 92877 dotnet/runtime.
Propriedades somente leitura
Para preencher propriedades de referência que são mutáveis, uma vez que a instância a que a propriedade faz referência não é substituída, a propriedade não precisa ter um setter. Esse comportamento significa que a desserialização também pode preencher propriedades somente leitura.
Observação
As propriedades struct ainda exigem setters, porque a instância é substituída por uma cópia modificada.
Exemplo de propriedade de coleção
Considere a mesma classe A
do exemplo de comportamento de substituição, mas desta vez anotada com uma preferência para preencher propriedades em vez de substituí-las:
[JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
class A
{
public List<int> Numbers1 { get; } = [1, 2, 3];
public List<int> Numbers2 { get; set; } = [1, 2, 3];
}
Se você executar o seguinte código de desserialização, ambos Numbers1
e Numbers2
contêm os valores 1, 2, 3, 4, 5 e 6:
A? a = JsonSerializer.Deserialize<A>("""{"Numbers1": [4,5,6], "Numbers2": [4,5,6]}""");
Exemplo de propriedade struct
A classe a seguir contém uma propriedade struct, S1
, cujo comportamento de desserialização é definido como Populate. Depois de executar esse código, c.S1.Value1
tem um valor de 10 (do construtor), e c.S1.Value2
tem um valor de 5 (do JSON).
C? c = JsonSerializer.Deserialize<C>("""{"S1": {"Value2": 5}}""");
class C
{
public C()
{
_s1 = new S
{
Value1 = 10
};
}
private S _s1;
[JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
public S S1
{
get { return _s1; }
set { _s1 = value; }
}
}
struct S
{
public int Value1 { get; set; }
public int Value2 { get; set; }
}
Se o comportamento Replace padrão fosse usado, c.S1.Value1
teria seu valor padrão 0 após a desserialização. Isso porque o construtor C()
seria chamado, definindo c.S1.Value1
como 10, mas então o valor de S1 seria substituído por uma nova instância. (c.S1.Value2
ainda seria 5, já que o JSON substitui o valor padrão.)
Como especificar
Há várias maneiras de especificar uma preferência para substituir ou preencher:
Use o atributo JsonObjectCreationHandlingAttribute para anotar no nível de tipo ou propriedade. Se você definir o atributo no nível do tipo e definir a propriedade Handling como Populate, o comportamento só se aplicará às propriedades onde o preenchimento for possível (por exemplo, os tipos de valor devem ter um setter).
Se desejar que a preferência de tipo seja Populate, mas quiser excluir uma ou mais propriedades desse comportamento, você poderá adicionar o atributo no nível de tipo e novamente no nível de propriedade para substituir o comportamento herdado. Um exemplo é mostrado no código seguinte.
// 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 (ou, para geração de origem, JsonSourceGenerationOptionsAttribute.PreferredObjectCreationHandling) para especificar uma preferência global.
var options = new JsonSerializerOptions { PreferredObjectCreationHandling = JsonObjectCreationHandling.Populate };