Примечания, допускающие значение NULL

Начиная с .NET 9, JsonSerializer поддерживает (ограниченную) поддержку ненулевого ссылочного типа в сериализации и десериализации. Эту поддержку можно переключить с помощью флага JsonSerializerOptions.RespectNullableAnnotations .

Например, следующий фрагмент кода выбрасывает JsonException исключение во время сериализации с таким сообщением:

Свойство или поле "Имя" в типе "Person" не позволяет получать значения NULL. Рассмотрите возможность обновления аннотации допустимости null.

    public static void RunIt()
    {
#nullable enable
        JsonSerializerOptions options = new()
        {
            RespectNullableAnnotations = true
        };

        Person invalidValue = new(Name: null!);
        JsonSerializer.Serialize(invalidValue, options);
    }

    record Person(string Name);

Аналогичным образом RespectNullableAnnotations обеспечивает контроль наличия или отсутствия значения null при десериализации. Следующий фрагмент кода вызывает JsonException исключение во время сериализации с сообщением типа:

Параметр конструктора "Имя" типа "Person" не разрешает значения NULL. Рассмотрите возможность обновления аннотации на допустимость null.

    public static void RunIt()
    {
#nullable enable
        JsonSerializerOptions options = new()
        {
            RespectNullableAnnotations = true
        };

        string json = """{"Name":null}""";
        JsonSerializer.Deserialize<Person>(json, options);
    }

    record Person(string Name);

Совет

  • Можно настроить нулевую допустимость на уровне отдельного свойства с помощью свойств IsGetNullable и IsSetNullable.
  • Компилятор C# использует атрибуты [NotNull], [AllowNull], [MaybeNull], и [DisallowNull] для точной настройки аннотаций в методах получения и заданиях. Эти атрибуты также распознаются этой System.Text.Json функцией. (Дополнительные сведения об атрибутах см. в разделе Атрибуты для статического анализа состояния NULL.)

Ограничения

Благодаря реализации ссылочных типов, не допускающих значение NULL, эта функция имеет некоторые важные ограничения. Ознакомьтесь с этими ограничениями перед включением функции. Корень проблемы заключается в том, что возможность ссылочного типа принимать значение null не имеет полноценного представления на уровне промежуточного языка (IL). Таким образом, выражения MyPoco и MyPoco? неотличимы с точки зрения времени выполнения. Хотя компилятор пытается компенсировать это путем создания метаданных атрибутов (см. пример на sharplab.io), эти метаданные ограничены аннотациями членов, не являющихся обобщенными, которые относятся к конкретному определению типа. Это ограничение является причиной того, что флаг проверяет только заметки nullability, которые присутствуют в не универсальных свойствах, полях и параметрах конструктора. System.Text.Json не поддерживает принудительное применение null в:

  • Типы верхнего уровня или тип, передаваемый при выполнении первого JsonSerializer.Deserialize() или JsonSerializer.Serialize() вызова.
  • Типы элементов коллекции, например, List<string> и List<string?> типы являются неотличимыми.
  • Все свойства, поля или параметры конструктора, которые являются универсальными.

Если вы хотите принудительно внедрить поддержку null в этих случаях, смоделируйте ваш тип как struct (поскольку они не допускают null-значений), или создайте пользовательский преобразователь, который переопределяет его HandleNull свойство на true.

Переключатель функции

Вы можете включить RespectNullableAnnotations параметр глобально с помощью System.Text.Json.Serialization.RespectNullableAnnotationsDefault переключателя функций. Добавьте следующий элемент MSBuild в файл проекта (например, CSPROJ-файл ):

<ItemGroup>
  <RuntimeHostConfigurationOption Include="System.Text.Json.Serialization.RespectNullableAnnotationsDefault" Value="true" />
</ItemGroup>

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

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

RespectNullableAnnotations не расширяет принудительное применение к неуказаемым значениям JSON, так как System.Text.Json обрабатывает обязательные и ненулевое свойства как ортгональные понятия. Например, следующий фрагмент кода не создает исключение во время десериализации:

public static void RunIt()
{
    JsonSerializerOptions options = new()
    {
        RespectNullableAnnotations = true
    };
    var result = JsonSerializer.Deserialize<MyPoco>("{}", options);
    Console.WriteLine(result.Name is null); // True.
}

class MyPoco
{
    public string Name { get; set; }
}

Это поведение связано с самим языком C#, где можно иметь необходимые свойства, допускающие значение NULL:

MyPoco poco = new() { Value = null }; // No compiler warnings.

class MyPoco
{
    public required string? Value { get; set; }
}

Кроме того, можно иметь необязательные свойства, не допускающие значения NULL:

class MyPoco
{
    public string Value { get; set; } = "default";
}

Та же ортогональность применяется к параметрам конструктора:

record MyPoco(
    string RequiredNonNullable,
    string? RequiredNullable,
    string OptionalNonNullable = "default",
    string? OptionalNullable = "default"
    );

Отсутствующие значения против значений NULL

Важно понимать различие между отсутствующими свойствами исвойствами JSON с явными null значениями при установке RespectNullableAnnotations. JavaScript отличается от undefined (отсутствующее свойство) и null (явное значение NULL). Однако .NET не имеет undefined концепции, поэтому оба варианта десериализируются в null .NET.

При десериализации, если RespectNullableAnnotations является true:

  • Явное значение NULL создает исключение для свойств, не допускающих значение NULL. Например, {"Name":null} вызывает исключение при десериализации в свойство string Name, которое не допускает значения null.

  • Отсутствующие свойства не вызывают исключения, даже для свойств, не допускающих значение NULL. Например, {} исключение не вызывается при десериализации в ненулевое string Name свойство. Сериализатор не задает свойство, оставляя его по умолчанию из конструктора. Для неинициализированного ссылочного типа, не допускающего значение NULL, это приводит к null, что вызывает предупреждение компилятором.

    В следующем коде показано, как отсутствующие свойства не вызывают исключение во время десериализации:

        public static void RunIt()
        {
    #nullable enable
            JsonSerializerOptions options = new()
            {
                RespectNullableAnnotations = true
            };
    
            // Missing property - does NOT throw an exception.
            string jsonMissing = """{}""";
            var resultMissing = JsonSerializer.Deserialize<Person>(jsonMissing, options);
            Console.WriteLine(resultMissing.Name is null); // True.
        }
    
        record Person(string Name);
    

Эта разница в поведении возникает, так как отсутствующие свойства обрабатываются как необязательные (не предоставленные), а явные null значения обрабатываются как предоставленные значения, которые нарушают ограничение, не допускающее значение NULL. Если необходимо обеспечить наличие свойства в JSON, используйте модификатор required или настройте свойство как обязательное с помощью JsonRequiredAttribute или модели контрактов.

См. также