Wypełnianie zainicjowanych właściwości

Począwszy od platformy .NET 8, można określić preferencję zastąpienia lub wypełnienia właściwości platformy .NET, gdy plik JSON jest deserializowany. Wyliczenie JsonObjectCreationHandling zawiera opcje obsługi tworzenia obiektów:

Zachowanie domyślne (zastępowanie)

Deserializator System.Text.Json zawsze tworzy nowe wystąpienie typu docelowego. Jednak mimo utworzenia nowego wystąpienia niektóre właściwości i pola mogą być już inicjowane w ramach budowy obiektu. Rozważ następujący typ:

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

Podczas tworzenia wystąpienia tej klasy Numbers1 wartość właściwości (i Numbers2) jest listą z trzema elementami (1, 2 i 3). Jeśli deserializujesz kod JSON do tego typu, domyślne zachowanie polega na tym, że wartości właściwości są zastępowane:

  • W przypadku Numbers1elementu , ponieważ jest on tylko do odczytu (bez ustawiacza), nadal ma wartości 1, 2 i 3 na liście.
  • W przypadku Numbers2elementu , który jest odczytem i zapisem, zostanie przydzielona nowa lista, a wartości z formatu JSON zostaną dodane.

Jeśli na przykład wykonasz następujący kod deserializacji, Numbers1 zawiera wartości 1, 2 i 3 oraz Numbers2 wartości 4, 5 i 6.

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

Wypełnianie zachowania

Począwszy od platformy .NET 8, można zmienić zachowanie deserializacji, aby zmodyfikować (wypełnić) właściwości i pola zamiast ich zastąpić:

  • W przypadku właściwości typu kolekcji obiekt jest ponownie używany bez czyszczenia. Jeśli kolekcja jest wstępnie wypełniona elementami, zostaną one wyświetlone w końcowym deserializacji wyniku wraz z wartościami z formatu JSON. Przykład właściwości Kolekcji można znaleźć w temacie Przykład właściwości kolekcji.

  • Dla właściwości, która jest obiektem z właściwościami, jego właściwości modyfikowalne są aktualizowane do wartości JSON, ale odwołanie do obiektu nie zmienia się.

  • W przypadku właściwości typu struktury efektywne zachowanie polega na tym, że dla jej właściwości modyfikowalnych wszystkie istniejące wartości są przechowywane, a dodawane są nowe wartości z formatu JSON. Jednak w przeciwieństwie do właściwości odwołania sam obiekt nie jest ponownie używany, ponieważ jest to typ wartości. Zamiast tego kopia struktury zostanie zmodyfikowana, a następnie ponownie przypisana do właściwości. Aby zapoznać się z przykładem właściwości Struct, zobacz przykład właściwości Struct.

    Właściwość struktury musi mieć element setter; w przeciwnym razie element InvalidOperationException jest zgłaszany w czasie wykonywania.

Uwaga

Zachowanie wypełniania obecnie nie działa w przypadku typów, które mają konstruktor sparametryzowany. Aby uzyskać więcej informacji, zobacz dotnet/runtime issue 92877(Problem 92877).

Właściwości tylko do odczytu

W przypadku wypełniania właściwości odwołania, które są modyfikowalne, ponieważ wystąpienie, do którego odwołuje się właściwość, nie jest zastępowane, właściwość nie musi mieć elementu ustawiającego. To zachowanie oznacza, że deserializacja może również wypełnić właściwości tylko do odczytu.

Uwaga

Właściwości struktury nadal wymagają modułów ustawiających, ponieważ wystąpienie zostało zastąpione zmodyfikowaną kopią.

Przykład właściwości kolekcji

Rozważmy tę samą klasę A z przykładu zachowania zamiany, ale tym razem z adnotacjami z preferencjami dotyczącymi wypełniania właściwości zamiast ich zastępowania:

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

Jeśli wykonasz następujący kod deserializacji, zarówno i Numbers1Numbers2 zawierają wartości 1, 2, 3, 4, 5 i 6:

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

Przykład właściwości struktury

Poniższa klasa zawiera właściwość struktury , S1której zachowanie deserializacji ma wartość Populate. Po wykonaniu tego kodu c.S1.Value1 ma wartość 10 (z konstruktora) i c.S1.Value2 ma wartość 5 (z formatu 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; }
}

Jeśli zamiast tego użyto domyślnego Replace zachowania, c.S1.Value1 wartość domyślna to 0 po deserializacji. Dzieje się tak, ponieważ konstruktor C() będzie wywoływany, ustawiając c.S1.Value1 wartość 10, ale następnie wartość S1 zostanie zamieniona na nowe wystąpienie. (c.S1.Value2 nadal wynosiłoby 5, ponieważ kod JSON zastępuje wartość domyślną).

Jak określić

Istnieje wiele sposobów określania preferencji dla zastępowania lub wypełniania:

  • Użyj atrybutu JsonObjectCreationHandlingAttribute , aby dodać adnotacje na poziomie typu lub właściwości. Jeśli ustawisz atrybut na poziomie typu i ustawisz jego Handling właściwość na Populatewartość , zachowanie będzie miało zastosowanie tylko do tych właściwości, w których jest możliwa populacja (na przykład typy wartości muszą mieć zestaw).

    Jeśli chcesz, aby preferencja dla całego typu to Populate, ale chcesz wykluczyć co najmniej jedną właściwość z tego zachowania, możesz dodać atrybut na poziomie typu i ponownie na poziomie właściwości, aby zastąpić zachowanie dziedziczone. Ten wzorzec jest wyświetlany w poniższym kodzie.

    // 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];
    }
    
  • Ustaw JsonSerializerOptions.PreferredObjectCreationHandling wartość (lub, dla generowania źródła), JsonSourceGenerationOptionsAttribute.PreferredObjectCreationHandlingaby określić preferencję globalną.

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