Skip to content

Commit 90fa7ea

Browse files
Copilotgewarren
andcommitted
Add documentation explaining missing vs null values in nullable annotations
Co-authored-by: gewarren <[email protected]>
1 parent aba5642 commit 90fa7ea

File tree

3 files changed

+49
-1
lines changed

3 files changed

+49
-1
lines changed

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,21 @@ record MyPoco(
8484
);
8585
```
8686

87+
## Missing values vs. null values
88+
89+
It's important to understand the distinction between missing JSON properties and properties with explicit `null` values when using <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.
90+
91+
When `RespectNullableAnnotations` is `true`:
92+
93+
- **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.
94+
- **Missing property**: Does NOT throw an exception, even for non-nullable properties. For example, `{}` does not throw an exception when deserializing to a non-nullable `string Name` property. The property is set to `null`.
95+
96+
The following code demonstrates this difference:
97+
98+
:::code language="csharp" source="snippets/nullable-annotations/Nullable.cs" id="MissingVsNull":::
99+
100+
This behavior 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.
101+
87102
## See also
88103

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

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,35 @@ 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+
// Explicit null value - DOES throw an exception.
78+
string jsonNull = """{"Name":null}""";
79+
try
80+
{
81+
JsonSerializer.Deserialize<Person>(jsonNull, options);
82+
}
83+
catch (JsonException ex)
84+
{
85+
Console.WriteLine(ex.Message); // The constructor parameter 'Name' on type 'Person' doesn't allow null values.
86+
}
87+
}
88+
89+
record Person(string Name);
90+
// </MissingVsNull>
91+
}
92+
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)