Compartilhar via


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