Поделиться через


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

Начиная с .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; }
}

Если вместо этого используется поведение по умолчанию, c.S1.Value1 значение по умолчанию Replace равно 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
    };