Preencher propriedades inicializadas

A partir do .NET 8, você pode especificar uma preferência para substituir ou preencher propriedades do .NET quando o JSON for desserializado. O JsonObjectCreationHandling enum fornece as opções de manipulação de criação de objetos:

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, o Numbers1 valor da propriedade (e Numbers2) é uma lista com três elementos (1, 2 e 3). Se você desserializar JSON para esse tipo, o comportamento padrão é que os valores de propriedade sã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 seguinte código de desserialização, Numbers1 contém os valores 1, 2 e 3 e Numbers2 contém 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 limpeza. 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 muda.

  • Para uma propriedade struct type, 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 da struct é modificada e, em seguida, reatribuída à propriedade. Para obter um exemplo, consulte Exemplo da propriedade Struct.

    Uma propriedade struct deve ter um setter; caso contrário, um InvalidOperationException é lançado em tempo de execução.

Nota

O comportamento de preenchimento atualmente não funciona para tipos que têm um construtor parametrizado. Para obter mais informações, consulte dotnet/runtime issue 92877.

Propriedades só de leitura

Para preencher propriedades de referência que são mutáveis, uma vez que a instância em que as referências de propriedade não são substituídas, a propriedade não precisa ter um setter. Esse comportamento significa que a desserialização também pode preencher propriedades somente leitura.

Nota

As propriedades Struct ainda exigem setters porque a instância é substituída por uma cópia modificada.

Exemplo de propriedade Collection

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 conter os valores 1, 2, 3, 4, 5 e 6:

A? a = JsonSerializer.Deserialize<A>("""{"Numbers1": [4,5,6], "Numbers2": [4,5,6]}""");

Exemplo da propriedade Struct

A classe a seguir contém uma propriedade struct, S1, cujo comportamento de desserialização é definido como Populate. Depois de executar este 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 padrão Replace fosse usado em vez disso, c.S1.Value1 teria seu valor padrão de 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 JsonObjectCreationHandlingAttribute atributo para anotar no nível do tipo ou da propriedade. Se você definir o atributo no nível do tipo e definir sua Handling propriedade como Populate, o comportamento só se aplicará às propriedades onde a população é possível (por exemplo, os tipos de valor devem ter um setter).

    Se desejar que a preferência Populatede tipo seja , 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 (ou, para geração de origem, JsonSourceGenerationOptionsAttribute.PreferredObjectCreationHandling) para especificar uma preferência global.

    var options = new JsonSerializerOptions
    {
        PreferredObjectCreationHandling = JsonObjectCreationHandling.Populate
    };