|
| 1 | +--- |
| 2 | +title: Respect nullable annotations |
| 3 | +description: "Learn how to configure serialization and deserialization to respect nullable annotations." |
| 4 | +ms.date: 10/22/2024 |
| 5 | +no-loc: [System.Text.Json, Newtonsoft.Json] |
| 6 | +--- |
| 7 | +# Respect nullable annotations |
| 8 | + |
| 9 | +Starting in .NET 9, <xref:System.Text.Json.JsonSerializer> has (limited) support for non-nullable reference type enforcement in both serialization and deserialization. You can toggle this support using the <xref:System.Text.Json.JsonSerializerOptions.RespectNullableAnnotations?displayProperty=nameWithType> flag. |
| 10 | + |
| 11 | +For example, the following code snippet throws a <xref:System.Text.Json.JsonException> during serialization with a message like: |
| 12 | + |
| 13 | +> The property or field 'Name' on type 'Person' doesn't allow getting null values. Consider updating its nullability annotation. |
| 14 | +
|
| 15 | +:::code language="csharp" source="snippets/nullable-annotations/Nullable.cs" id="Serialization"::: |
| 16 | + |
| 17 | +Similarly, <xref:System.Text.Json.JsonSerializerOptions.RespectNullableAnnotations> enforces nullability on deserialization. The following code snippet throws a <xref:System.Text.Json.JsonException> during serialization with a message like: |
| 18 | + |
| 19 | +> The constructor parameter 'Name' on type 'Person' doesn't allow null values. Consider updating its nullability annotation. |
| 20 | +
|
| 21 | +:::code language="csharp" source="snippets/nullable-annotations/Nullable.cs" id="Deserialization"::: |
| 22 | + |
| 23 | +> [!TIP] |
| 24 | +> You can configure nullability at an individual property level using the <xref:System.Text.Json.Serialization.Metadata.JsonPropertyInfo.IsGetNullable> and <xref:System.Text.Json.Serialization.Metadata.JsonPropertyInfo.IsSetNullable> properties. |
| 25 | +
|
| 26 | +## Limitations |
| 27 | + |
| 28 | +Due to how non-nullable reference types are implemented, this feature comes with some important limitations. Familiarize yourself with these limitations before turning the feature on. The root of the issue is that reference type nullability has no first-class representation in intermediate language (IL). As such, the expressions `MyPoco` and `MyPoco?` are indistinguishable from the perspective of run-time reflection. While the compiler will try to make up for that by [emitting attribute metadata where possible](https://sharplab.io/#v2:D4AQTAjAsAULBOBTAxge3gEwAQFkCeACqmgBQgQAMWAcqgC7UCuANswMp3wCWAdgOYAaLOQoB+Gi2YBDAEbNEHbvwCUAbiA=), this metadata is restricted to non-generic member annotations that are scoped to a particular type definition. This is the reason that the flag only validates nullability annotations that are present on non-generic properties, fields, and constructor parameters. System.Text.Json does not support nullability enforcement on: |
| 29 | + |
| 30 | +- Top-level types, or the type that's passed when making the first `JsonSerializer.(De)serialize` call. |
| 31 | +- Collection element types—the `List<string>` and `List<string?>` types are indistinguishable. |
| 32 | +- Any properties, fields, or constructor parameters that are generic. |
| 33 | + |
| 34 | +If you want to add nullability enforcement in these cases, either model your type to be a struct (since they don't admit null values), or author a custom converter that overrides its <xref:System.Text.Json.Serialization.JsonConverter`1.HandleNull> property to `true`. |
| 35 | + |
| 36 | +## Feature switch |
| 37 | + |
| 38 | +You can turn on the `RespectNullableAnnotations` setting globally using the `System.Text.Json.Serialization.RespectNullableAnnotationsDefault` feature switch. Add the following MSBuild item to your project file (for example, *.csproj* file): |
| 39 | + |
| 40 | +```xml |
| 41 | +<ItemGroup> |
| 42 | + <RuntimeHostConfigurationOption Include="System.Text.Json.Serialization.RespectNullableAnnotationsDefault" Value="true" /> |
| 43 | +</ItemGroup> |
| 44 | +``` |
| 45 | + |
| 46 | +The `RespectNullableAnnotationsDefault` API was implemented as an opt-in flag in .NET 9 to avoid breaking existing applications. If you're writing a new application, it's highly recommended that you enable this flag in your code. |
| 47 | + |
| 48 | +## Relationship between nullable and optional parameters |
| 49 | + |
| 50 | +<xref:System.Text.Json.JsonSerializerOptions.RespectNullableAnnotations> doesn't extend enforcement to unspecified JSON values, because System.Text.Json treats required and non-nullable properties as orthogonal concepts. For example, the following code snippet doesn't throw an exception during deserialization: |
| 51 | + |
| 52 | +:::code language="csharp" source="snippets/nullable-annotations/Nullable.cs" id="Unspecified"::: |
| 53 | + |
| 54 | +This behavior stems from the C# language itself, where you can have required properties that are nullable: |
| 55 | + |
| 56 | +```csharp |
| 57 | +MyPoco poco = new() { Value = null }; // No compiler warnings. |
| 58 | +
|
| 59 | +class MyPoco |
| 60 | +{ |
| 61 | + public required string? Value { get; set; } |
| 62 | +} |
| 63 | +``` |
| 64 | + |
| 65 | +And you can also have optional properties that are non-nullable: |
| 66 | + |
| 67 | +```csharp |
| 68 | +class MyPoco |
| 69 | +{ |
| 70 | + public string Value { get; set; } = "default"; |
| 71 | +} |
| 72 | +``` |
| 73 | + |
| 74 | +The same orthogonality applies to constructor parameters: |
| 75 | + |
| 76 | +```csharp |
| 77 | +record MyPoco( |
| 78 | + string RequiredNonNullable, |
| 79 | + string? RequiredNullable, |
| 80 | + string OptionalNonNullable = "default", |
| 81 | + string? OptionalNullable = "default"); |
| 82 | +``` |
| 83 | + |
| 84 | +## See also |
| 85 | + |
| 86 | +- [Non-optional constructor parameters](required-properties.md#non-optional-constructor-parameters) |
0 commit comments