diff --git a/docs/standard/serialization/system-text-json/nullable-annotations.md b/docs/standard/serialization/system-text-json/nullable-annotations.md index 8a5f7d65f44ad..06193824a24c8 100644 --- a/docs/standard/serialization/system-text-json/nullable-annotations.md +++ b/docs/standard/serialization/system-text-json/nullable-annotations.md @@ -1,8 +1,9 @@ --- title: Respect nullable annotations description: "Learn how to configure serialization and deserialization to respect nullable annotations." -ms.date: 10/22/2024 +ms.date: 10/20/2025 no-loc: [System.Text.Json, Newtonsoft.Json] +ai-usage: ai-assisted --- # Respect nullable annotations @@ -84,6 +85,21 @@ record MyPoco( ); ``` +## Missing values versus null values + +It's important to understand the distinction between *missing JSON properties* and *properties with explicit `null` values* when you set . JavaScript distinguishes between `undefined` (missing property) and `null` (explicit null value). However, .NET doesn't have an `undefined` concept, so both cases deserialize to `null` in .NET. + +During deserialization, when `RespectNullableAnnotations` is `true`: + +- An **explicit null value** throws an exception for non-nullable properties. For example, `{"Name":null}` throws an exception when deserializing to a non-nullable `string Name` property. +- A **missing property** doesn't throw an exception, even for non-nullable properties. For example, `{}` doesn't throw an exception when deserializing to a non-nullable `string Name` property. The serializer doesn't set the property, leaving it at its default value from the constructor. For an uninitialized non-nullable reference type, this results in `null`, which triggers a compiler warning. + + The following code shows how a missing property does NOT throw an exception during deserialization: + + :::code language="csharp" source="snippets/nullable-annotations/Nullable.cs" id="MissingVsNull"::: + +This behavior difference occurs because missing properties are treated as optional (not provided), while explicit `null` values are treated as provided values that violate the non-nullable constraint. If you need to enforce that a property must be present in the JSON, use the `required` modifier or configure the property as required using or the contracts model. + ## See also - [Non-optional constructor parameters](required-properties.md#non-optional-constructor-parameters) diff --git a/docs/standard/serialization/system-text-json/snippets/nullable-annotations/Nullable.cs b/docs/standard/serialization/system-text-json/snippets/nullable-annotations/Nullable.cs index 99cbf61b9a71f..86c2fc086f4de 100644 --- a/docs/standard/serialization/system-text-json/snippets/nullable-annotations/Nullable.cs +++ b/docs/standard/serialization/system-text-json/snippets/nullable-annotations/Nullable.cs @@ -58,3 +58,24 @@ record Person(string Name); // } +public static class Nullable4 +{ + // + public static void RunIt() + { +#nullable enable + JsonSerializerOptions options = new() + { + RespectNullableAnnotations = true + }; + + // Missing property - does NOT throw an exception. + string jsonMissing = """{}"""; + var resultMissing = JsonSerializer.Deserialize(jsonMissing, options); + Console.WriteLine(resultMissing.Name is null); // True. + } + + record Person(string Name); + // +} + diff --git a/docs/standard/serialization/system-text-json/snippets/nullable-annotations/Program.cs b/docs/standard/serialization/system-text-json/snippets/nullable-annotations/Program.cs index 816681470e389..82413ada4b464 100644 --- a/docs/standard/serialization/system-text-json/snippets/nullable-annotations/Program.cs +++ b/docs/standard/serialization/system-text-json/snippets/nullable-annotations/Program.cs @@ -1,3 +1,4 @@ //Nullable1.RunIt(); //Nullable2.RunIt(); -Nullable3.RunIt(); +//Nullable3.RunIt(); +Nullable4.RunIt();