Share via


Rellenar las propiedades inicializadas

A partir de .NET 8, puede especificar una preferencia para reemplazar o rellenar las propiedades de .NET cuando se deserializa JSON. La enumeración JsonObjectCreationHandling proporciona las opciones de control de creación de objetos:

Comportamiento predeterminado (reemplazar)

El deserializador System.Text.Json siempre crea una nueva instancia del tipo de destino. Sin embargo, aunque se crea una nueva instancia, es posible que algunas propiedades y campos ya se inicialicen como parte de la construcción del objeto. Considere el siguiente tipo :

class A
{
    public List<int> Numbers1 { get; } = [1, 2, 3];
    public List<int> Numbers2 { get; set; } = [1, 2, 3];
}

Al crear una instancia de esta clase, el valor de la propiedad Numbers1 (y Numbers2) es una lista con tres elementos (1, 2 y 3). Si deserializa JSON a este tipo, el comportamiento de predeterminado es que los valores de propiedad se reemplazan:

  • Para Numbers1, ya que es de solo lectura (sin establecedor), sigue teniendo los valores 1, 2 y 3 en su lista.
  • Para Numbers2, que es de lectura y escritura, se asigna una nueva lista y se agregan los valores de JSON.

Por ejemplo, si ejecuta el siguiente código de deserialización, Numbers1 contiene los valores 1, 2 y 3 y Numbers2 contiene los valores 4, 5 y 6.

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

Rellenar el comportamiento

A partir de .NET 8, puede cambiar el comportamiento de deserialización para modificar (rellenar) propiedades y campos en lugar de reemplazarlos:

  • Para una propiedad de tipo de colección, el objeto se reutiliza sin borrar. Si la colección se rellena previamente con elementos, se mostrarán en el resultado deserializado final junto con los valores de JSON. Para obtener un ejemplo, vea ejemplo de la propiedad Collection.

  • Para una propiedad que es un objeto con propiedades, sus propiedades mutables se actualizan a los valores JSON, pero la referencia de objeto en sí no cambia.

  • Para una propiedad de tipo de estructura, el comportamiento efectivo es que, para sus propiedades mutables, se conservan los valores existentes y se agregan nuevos valores del JSON. Sin embargo, a diferencia de una propiedad de referencia, el propio objeto no se reutiliza, ya que es un tipo de valor. En su lugar, se modifica una copia de la estructura y, a continuación, se reasigna a la propiedad. Para obtener un ejemplo, vea ejemplo de la propiedad Struct.

    Una propiedad de estructura debe tener un establecedor; de lo contrario, se produce una InvalidOperationException en tiempo de ejecución.

Nota:

El comportamiento de relleno no funciona actualmente para los tipos que tienen un constructor con parámetros. Para más información, vea la incidencia 92877 de dotnet/runtime.

Propiedades de solo lectura

Para rellenar las propiedades de referencia que son mutables, ya que la instancia a la que hace referencia la propiedad no se reemplaza, la propiedad no necesita tener un establecedor. Este comportamiento significa que la deserialización también puede rellenar las propiedades de solo lectura.

Nota:

Las propiedades de estructura siguen necesitando establecedores porque la instancia se reemplaza por una copia modificada.

Ejemplo de la propiedad Collection

Considere la misma clase A del ejemplo de comportamiento de reemplazo, pero esta vez anotada con una preferencia para rellenar las propiedades en lugar de reemplazarlas:

[JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
class A
{
    public List<int> Numbers1 { get; } = [1, 2, 3];
    public List<int> Numbers2 { get; set; } = [1, 2, 3];
}

Si ejecuta el código de deserialización siguiente, tanto Numbers1 como Numbers2 contienen los valores 1, 2, 3, 4, 5 y 6:

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

Ejemplo de la propiedad Struct

La siguiente clase contiene una propiedad de estructura, S1, cuyo comportamiento de deserialización se establece en Populate. Después de ejecutar este código, c.S1.Value1 tiene un valor de 10 (del constructor) y c.S1.Value2 tiene un valor de 5 (del 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; }
}

Si en su lugar se usó el comportamiento de Replace predeterminado, c.S1.Value1 tendría su valor predeterminado 0 después de la deserialización. Esto se debe a que se llamaría al constructor C(), estableciendo c.S1.Value1 en 10, pero el valor de S1 se reemplazaría por una nueva instancia. (c.S1.Value2 seguiría siendo 5, ya que JSON reemplaza el valor predeterminado.)

Cómo especificar

Hay varias maneras de especificar una preferencia para reemplazar o rellenar:

  • Use el atributo JsonObjectCreationHandlingAttribute para anotar en el nivel de tipo o propiedad. Si establece el atributo en el nivel de tipo y establece su propiedad Handling en Populate, el comportamiento solo se aplicará a esas propiedades en las que sea posible el rellenado (por ejemplo, los tipos de valor deben tener un establecedor).

    Si desea que la preferencia de todo el tipo sea Populate, pero desea excluir una o varias propiedades de ese comportamiento, puede agregar el atributo en el nivel de tipo y de nuevo en el nivel de propiedad para invalidar el comportamiento heredado. Ese patrón se muestra en el código siguiente.

    // 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];
    }
    
  • Establezca JsonSerializerOptions.PreferredObjectCreationHandling (o, para la generación de origen, JsonSourceGenerationOptionsAttribute.PreferredObjectCreationHandling) para especificar una preferencia global.

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