Share via


Popolare le proprietà inizializzate

A partire da .NET 8, è possibile specificare una preferenza per sostituire o popolare le proprietà .NET quando JSON viene deserializzato. L'enumerazione JsonObjectCreationHandling fornisce le scelte di gestione della creazione di oggetti:

Comportamento predefinito (sostituzione)

Il deserializzatore System.Text.Json crea sempre una nuova istanza del tipo di destinazione. Tuttavia, anche se viene creata una nuova istanza, alcune proprietà e campi potrebbero essere già inizializzati come parte della costruzione dell'oggetto. Considera il tipo seguente:

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

Quando si crea un'istanza di questa classe, il valore della proprietà Numbers1 (e Numbers2) è un elenco con tre elementi (1, 2 e 3). Se si deserializza JSON in questo tipo, il comportamento predefinito è che i valori delle proprietà vengono sostituiti:

  • Per Numbers1, poiché è di sola lettura (nessun setter), presenta ancora i valori 1, 2 e 3 nel relativo elenco.
  • Per Numbers2, che è di lettura/scrittura, viene allocato un nuovo elenco e vengono aggiunti i valori del codice JSON.

Ad esempio, se si esegue il codice di deserializzazione seguente, Numbers1 contiene i valori 1, 2 e 3 e Numbers2 contiene i valori 4, 5 e 6.

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

Comportamento popolamento

A partire da .NET 8, è possibile modificare il comportamento di deserializzazione in modo da modificare (popolare) le proprietà e i campi anziché sostituirli:

  • Per una proprietà del tipo di raccolta, l'oggetto viene riutilizzato senza cancellazione. Se la raccolta è prepopolata con gli elementi, verranno visualizzati nel risultato deserializzato finale insieme ai valori del formato JSON. Per un esempio, vedere Esempio di proprietà raccolta.

  • Per una proprietà che è un oggetto con proprietà, le relative proprietà modificabili vengono aggiornate ai valori JSON, ma il riferimento all'oggetto stesso non cambia.

  • Per una proprietà di tipo struct, il comportamento effettivo è che per le relative proprietà modificabili, tutti i valori esistenti vengono mantenuti e vengono aggiunti nuovi valori dal formato JSON. Tuttavia, a differenza di una proprietà di riferimento, l'oggetto stesso non viene riutilizzato perché è un tipo di valore. Viene invece modificata una copia dello struct e quindi riassegnata alla proprietà. Per un esempio, vedere Esempio di proprietà struct.

    Una proprietà struct deve presentare un setter; in caso contrario, viene generato un elemento InvalidOperationException in fase di esecuzione.

Nota

Il comportamento di popolamento attualmente non funziona per i tipi con un costruttore con parametri. Per altre informazioni, vedere Problema 92877 dotnet/runtime.

Proprietà di sola lettura

Per popolare le proprietà di riferimento modificabili, poiché l'istanza a cui fa riferimento la proprietà non viene sostituita, la proprietà non deve presentare un setter. Questo comportamento indica che la deserializzazione può anche popolare le proprietà di sola lettura.

Nota

Le proprietà dello struct richiedono ancora setter perché l'istanza viene sostituita con una copia modificata.

Esempio di proprietà raccolta

Si consideri la stessa classe A dell'esempio di comportamento di sostituzione, ma questa volta con annotazioni con una preferenza per le proprietà di popolazione anziché la sostituzione:

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

Se si esegue il codice di deserializzazione seguente, sia Numbers1 che Numbers2 contengono i valori 1, 2, 3, 4, 5 e 6:

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

Esempio di proprietà Struct

La classe seguente contiene una proprietà struct, S1, il cui comportamento di deserializzazione è impostato su Populate. Dopo l'esecuzione di questo codice, c.S1.Value1 ha un valore pari a 10 (dal costruttore) e c.S1.Value2 ha un valore pari a 5 (da 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 invece è stato usato il comportamento predefinito Replace, c.S1.Value1 avrà il valore predefinito 0 dopo la deserializzazione. Questo perché il costruttore C() viene chiamato, impostando c.S1.Value1 su 10, ma il valore di S1 verrà sostituito con una nuova istanza. (c.S1.Value2 sarebbe ancora pari a 5, perché JSON sostituisce il valore predefinito.)

Come specificare

Esistono diversi modi per specificare una preferenza per la sostituzione o la popolazione:

  • Utilizzare l'attributo JsonObjectCreationHandlingAttribute per annotare a livello di tipo o proprietà. Se si imposta l'attributo a livello di tipo e si imposta la relativa proprietà Handling su Populate, il comportamento verrà applicato solo alle proprietà in cui è possibile eseguire il popolamento (ad esempio, i tipi valore devono avere un setter).

    Se si desidera che la preferenza a livello di tipo sia Populate, ma si desidera escludere una o più proprietà da tale comportamento, è possibile aggiungere l'attributo a livello di tipo e di nuovo a livello di proprietà per eseguire l'override del comportamento ereditato. Questo modello è illustrato nel codice seguente.

    // 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];
    }
    
  • Impostare JsonSerializerOptions.PreferredObjectCreationHandling (o, per la generazione di origine, JsonSourceGenerationOptionsAttribute.PreferredObjectCreationHandling) per specificare una preferenza globale.

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