Skip to content

Commit a416869

Browse files
authored
Document missing vs. null value behavior in RespectNullableAnnotations (#49378)
1 parent 6e6b9ec commit a416869

File tree

3 files changed

+40
-2
lines changed

3 files changed

+40
-2
lines changed

docs/standard/serialization/system-text-json/nullable-annotations.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
---
22
title: Respect nullable annotations
33
description: "Learn how to configure serialization and deserialization to respect nullable annotations."
4-
ms.date: 10/22/2024
4+
ms.date: 10/20/2025
55
no-loc: [System.Text.Json, Newtonsoft.Json]
6+
ai-usage: ai-assisted
67
---
78
# Respect nullable annotations
89

@@ -84,6 +85,21 @@ record MyPoco(
8485
);
8586
```
8687

88+
## Missing values versus null values
89+
90+
It's important to understand the distinction between *missing JSON properties* and *properties with explicit `null` values* when you set <xref:System.Text.Json.JsonSerializerOptions.RespectNullableAnnotations>. 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.
91+
92+
During deserialization, when `RespectNullableAnnotations` is `true`:
93+
94+
- 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.
95+
- 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.
96+
97+
The following code shows how a missing property does NOT throw an exception during deserialization:
98+
99+
:::code language="csharp" source="snippets/nullable-annotations/Nullable.cs" id="MissingVsNull":::
100+
101+
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 <xref:System.Text.Json.Serialization.JsonRequiredAttribute> or the contracts model.
102+
87103
## See also
88104

89105
- [Non-optional constructor parameters](required-properties.md#non-optional-constructor-parameters)

docs/standard/serialization/system-text-json/snippets/nullable-annotations/Nullable.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,24 @@ record Person(string Name);
5858
// </Deserialization>
5959
}
6060

61+
public static class Nullable4
62+
{
63+
// <MissingVsNull>
64+
public static void RunIt()
65+
{
66+
#nullable enable
67+
JsonSerializerOptions options = new()
68+
{
69+
RespectNullableAnnotations = true
70+
};
71+
72+
// Missing property - does NOT throw an exception.
73+
string jsonMissing = """{}""";
74+
var resultMissing = JsonSerializer.Deserialize<Person>(jsonMissing, options);
75+
Console.WriteLine(resultMissing.Name is null); // True.
76+
}
77+
78+
record Person(string Name);
79+
// </MissingVsNull>
80+
}
81+
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
//Nullable1.RunIt();
22
//Nullable2.RunIt();
3-
Nullable3.RunIt();
3+
//Nullable3.RunIt();
4+
Nullable4.RunIt();

0 commit comments

Comments
 (0)