Respektovat nulovatelné anotace

Počínaje .NET 9 má JsonSerializer (omezenou) podporu pro vynucení nereferenčních typů, které nelze nastavit na null, jak při serializaci, tak i deserializaci. Tuto podporu můžete přepnout pomocí příznaku JsonSerializerOptions.RespectNullableAnnotations .

Například následující fragment kódu vyvolá JsonException během serializace zprávu jako:

Vlastnost nebo pole Jméno typu Person neumožňuje získání hodnot null. Zvažte aktualizaci anotace nulovatelnosti.

    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 Podobně vynucuje u deserializace nulovou hodnotu. Následující fragment kódu vyvolá JsonException během serializace s chybovou zprávou například:

Parametr konstruktoru Name u typu Person neumožňuje hodnoty null. Zvažte aktualizaci anotací nulovatelnosti.

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

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

    record Person(string Name);

Návod

Omezení

Vzhledem k tomu, jak jsou implementovány nenulovatelné odkazové typy, má tato funkce některá důležitá omezení. Před zapnutím této funkce se seznamte s těmito omezeními. Kořenem problému je, že referenční typ nullability nemá žádnou prvotřídní reprezentaci v přechodném jazyce (IL). Výrazy MyPoco a MyPoco? jsou tak nerozlišitelné od pohledu reflexe modulu runtime. Zatímco se kompilátor snaží to kompenzovat generováním metadat atributů (viz sharplab.io příklad), tato metadata jsou omezena na ne-generické anotace členů, které jsou omezeny na konkrétní definici typu. Toto omezení je důvodem, proč příznak ověřuje pouze poznámky nullability, které jsou přítomné u ne generických vlastností, polí a parametrů konstruktoru. System.Text.Json nepodporuje vynucení nullability pro:

  • Nejvyšší typy nebo typ, který se předává, když se provádí první volání JsonSerializer.Deserialize() nebo JsonSerializer.Serialize().
  • Typy elementů kolekce , například List<string> a List<string?> typy jsou nerozlišitelné.
  • Všechny vlastnosti, pole nebo parametry konstruktoru, které jsou obecné.

Pokud chcete v těchto případech přidat vynucení nullability, můžete buď modelovat typ jako strukturu (protože nepřipouštějí hodnoty null), nebo vytvořit vlastní převaděč, který přepíše jeho HandleNull vlastnost na true.

Přepínač funkce

Nastavení můžete zapnout RespectNullableAnnotations globálně pomocí System.Text.Json.Serialization.RespectNullableAnnotationsDefault přepínače funkcí. Do souboru projektu přidejte následující položku MSBuild (například soubor .csproj ):

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

Rozhraní RespectNullableAnnotationsDefault API bylo implementováno jako příznak výslovného souhlasu v .NET 9, aby nedocházelo k přerušení stávajících aplikací. Pokud píšete novou aplikaci, důrazně doporučujeme povolit tento příznak ve svém kódu.

Vztah mezi parametry s možnou hodnotou null a volitelnými parametry

RespectNullableAnnotations nerozšíří vynucení na nezadané hodnoty JSON, protože System.Text.Json považuje požadované a nenulovatelné vlastnosti jako orthogonální koncepty. Například následující fragment kódu při deserializaci nevyvolá výjimku:

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

Toto chování vychází ze samotného jazyka C#, kde můžete mít požadované vlastnosti, které mají hodnotu null:

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

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

Můžete také mít volitelné vlastnosti, které nemají hodnotu null:

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

Stejná orthogonalita platí pro parametry konstruktoru:

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

Chybějící hodnoty a hodnoty null

Je důležité pochopit rozdíl mezi chybějícími vlastnostmi JSON a vlastnostmi s explicitními null hodnotami při nastavování RespectNullableAnnotations. JavaScript rozlišuje mezi undefined (chybějící vlastností) a null (explicitní hodnotou null). .NET ale nemá koncept undefined, takže oba případy se deserializují na null v .NET.

Při deserializaci, kdy RespectNullableAnnotations je true:

  • Explicitní hodnota null vyvolá výjimku pro nenulové vlastnosti. Například {"Name":null} vyvolá výjimku při deserializaci do nenulovatelných string Name vlastností.

  • Chybějící vlastnost nevyvolá výjimku, a to ani u nenulových vlastností. {} Například nevyvolá výjimku při deserializaci na nenulovou string Name vlastnost. Serializátor nenastaví vlastnost a ponechá ji ve své výchozí hodnotě z konstruktoru. U neinicializovaného nenulového referenčního typu to má za následek null, což aktivuje upozornění kompilátoru.

    Následující kód ukazuje, jak chybějící vlastnost nevyvolá výjimku během deserializace:

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

K tomuto rozdílu chování dochází, protože chybějící vlastnosti jsou považovány za volitelné (nezadaná), zatímco explicitní null hodnoty jsou považovány za zadané hodnoty, které porušují omezení bez hodnoty null. Pokud potřebujete vynutit, aby vlastnost byla přítomna ve formátu JSON, použijte required modifikátor nebo nakonfigurujte vlastnost podle potřeby pomocí JsonRequiredAttribute modelu kontraktů.

Viz také