Compartilhar via


Respeitar anotações anuláveis

A partir do .NET 9, o JsonSerializer oferece suporte (limitado) para imposição de tipo de referência não anulável na serialização na desserialização. Você pode alternar esse suporte com o sinalizador JsonSerializerOptions.RespectNullableAnnotations.

Por exemplo, o seguinte trecho de código lança um JsonException durante a serialização com uma mensagem como:

A propriedade ou campo "Name" no tipo "Person" não permite obter valores nulos. Considere atualizar sua anotação de nulidade.

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

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

    record Person(string Name);

Da mesma forma, o RespectNullableAnnotations impõe nulidade na desserialização. O seguinte trecho de código lança um JsonException durante a serialização com uma mensagem como:

O parâmetro de construtor "Name" no tipo "Person" não permite valores nulos. Considere atualizar sua anotação de nulidade.

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

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

    record Person(string Name);

Dica

Limitações

Devido à forma como os tipos de referência não anuláveis ​​são implementados, esse recurso vem com algumas limitações importantes. Saiba quais são essas limitações antes de ativar o recurso. A raiz do problema é que a nulidade do tipo de referência não tem representação de primeira classe na IL (linguagem intermediária). Como tal, as expressões MyPoco e MyPoco? são indistinguíveis da perspectiva da reflexão em tempo de execução. Embora o compilador tente compensar isso emitindo metadados de atributo (consulte o exemplo sharplab.io), esses metadados são restritos a anotações de membro não genéricas que têm como escopo uma definição de tipo específica. Essa limitação é a razão pela qual o sinalizador valida apenas anotações de nulidade que estão presentes em propriedades, campos e parâmetros de construtor não genéricos. System.Text.Json não tem suporte para aplicação de nulidade em:

  • Tipos de nível superior ou o tipo que é passado ao fazer a primeira chamada JsonSerializer.Deserialize() ou JsonSerializer.Serialize().
  • Tipos de elementos de coleção, por exemplo, os tipos List<string> e List<string?>, são indistinguíveis.
  • Quaisquer propriedades, campos ou parâmetros de construtor que são genéricos.

Se você quiser adicionar a imposição de nulidade nesses casos, modele seu tipo para ser um struct (já que eles não admitem valores nulos) ou crie um conversor personalizado que substitua sua propriedade HandleNull para true.

Opção de recurso

Você pode ativar a configuração RespectNullableAnnotations globalmente com a opção de recurso System.Text.Json.Serialization.RespectNullableAnnotationsDefault. Adicione o seguinte item do MSBuild ao arquivo de projeto (por exemplo, arquivo .csproj):

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

A API RespectNullableAnnotationsDefault foi implementada como um sinalizador de aceitação no .NET 9 para evitar a interrupção de aplicativos existentes. Se você estiver escrevendo um novo aplicativo, é altamente recomendável habilitar esse sinalizador no seu código.

Relação entre parâmetros anuláveis e opcionais

O RespectNullableAnnotations não estende a imposição a valores JSON não especificados, pois System.Text.Json trata as propriedades necessárias e não anuláveis como conceitos ortogonais. Por exemplo, o trecho de código a seguir não gera uma exceção durante a desserialização:

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; }
}

Esse comportamento decorre da própria linguagem C#, onde você pode ter as propriedades necessárias que podem ser anuladas:

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

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

E você também pode ter propriedades opcionais que não permitem valor nulo:

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

A mesma ortogonalidade é aplicada aos parâmetros do construtor:

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

Confira também