Бөлісу құралы:


Заполнение инициализированных свойств

Начиная с .NET 8, можно указать предпочтения для замены или заполнения свойств .NET при десериализации JSON. Перечисление JsonObjectCreationHandling предоставляет варианты обработки создания объекта:

Поведение по умолчанию (замена)

Десериализатор System.Text.Json всегда создает новый экземпляр целевого типа. Однако, несмотря на создание нового экземпляра, некоторые свойства и поля уже могут быть инициализированы как часть конструкции объекта. Рассмотрим следующий тип:

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

При создании экземпляра этого класса значение свойства Numbers1 и Numbers2 — это список с тремя элементами (1, 2 и 3). При десериализации JSON до этого типа поведение по умолчанию заключается в том, что значения свойств заменяются:

  • Для Numbers1, так как он только для чтения (без задания), он по-прежнему имеет значения 1, 2 и 3 в своем списке.
  • Для Numbers2, который является свойством с правами чтения и записи, создается новый список, и к нему добавляются значения из JSON.

Например, если выполнить следующий код десериализации, Numbers1 содержит значения 1, 2 и 3 и Numbers2 содержит значения 4, 5 и 6.

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

Поведение при заполнении

Начиная с .NET 8, можно изменить поведение десериализации, чтобы изменить (заполнять) свойства и поля вместо замены:

  • Для свойства типа коллекции объект повторно используется без очистки. Если коллекция предварительно заполнена элементами, они будут отображаться в окончательном десериализированном результате вместе со значениями из JSON. Пример см. в примере свойства Collection.

  • Для свойства, которое является объектом со свойствами, его изменяемые свойства обновляются до значений JSON, но ссылка на объект не изменяется.

  • Для свойства типа структуры эффективное поведение заключается в том, что для его изменяемых свойств сохраняются все существующие значения и добавляются новые значения из JSON. Однако, в отличие от эталонного свойства, сам объект не используется повторно, так как он является типом значения. Вместо этого копия структуры изменяется, а затем переназначается свойству. Пример см. в примере свойства структуры.

    Свойство структуры должно иметь метод задания; InvalidOperationException в противном случае вызывается во время выполнения.

Замечание

В настоящее время поведение заполнения не работает для типов с параметризованным конструктором. Дополнительные сведения см. в статье dotnet/runtime issue 92877.

Свойства только для чтения

Для заполнения изменяемых ссылочных свойств, поскольку экземпляр, на который ссылается свойство, не заменяется, свойству не требуется сеттер. Это поведение означает, что десериализация может также заполнять свойства только для чтения.

Замечание

Свойства структуры по-прежнему требуют наличия сеттеров, так как экземпляр заменяется модифицированной копией.

Пример свойства коллекции

Рассмотрим тот же класс A из примера поведения замены, но на этот раз аннотирован с предпочтением заполнения свойств вместо их замены.

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

Если выполнить следующий код десериализации, то и Numbers1, и Numbers2 содержат значения 1, 2, 3, 4, 5 и 6.

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

Пример свойства структуры

Следующий класс содержит свойство структуры S1, для которого задано Populate поведение десериализации. После выполнения этого кода c.S1.Value1 имеет значение 10 (от конструктора) и c.S1.Value2 имеет значение 5 (из 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; }
}

Если было бы использовано поведение Replace по умолчанию, c.S1.Value1 имело бы значение по умолчанию 0 после десериализации. Это связано с тем, что конструктор C() будет вызван, установив c.S1.Value1 в значение 10, но затем экземпляр S1 будет заменён новым. (c.S1.Value2 по-прежнему будет равно 5, так как JSON заменяет значение по умолчанию.)

Как указать

Существует несколько способов указать предпочтения для замены или заполнения:

  • JsonObjectCreationHandlingAttribute Используйте атрибут для анотации на уровне типа или свойства. Если атрибут задан на уровне типа и его свойство Handling установлено в Populate, поведение будет применяться только к тем свойствам, где возможна инициализация (например, типы значений должны иметь сеттер).

    Если вы хотите, чтобы предпочттельный параметр на уровне типа был Populate, но хотите исключить одно или несколько свойств из этого поведения, можно добавить атрибут на уровне типа и снова на уровне свойств, чтобы переопределить унаследованное поведение. Этот шаблон показан в следующем коде.

    // 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];
    }
    
  • Задайте JsonSerializerOptions.PreferredObjectCreationHandling (или, JsonSourceGenerationOptionsAttribute.PreferredObjectCreationHandling для генерации исходного кода) чтобы указать глобальные предпочтения.

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