Skip to content

Commit 46d3873

Browse files
authored
More STJ updates for .NET 9 (#43168)
1 parent e21d6fb commit 46d3873

File tree

16 files changed

+432
-110
lines changed

16 files changed

+432
-110
lines changed

docs/core/whats-new/dotnet-9/libraries.md

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -499,22 +499,11 @@ For more information, see [JSON schema exporter](../../../standard/serialization
499499

500500
<xref:System.Text.Json> now recognizes nullability annotations of properties and can be configured to enforce those during serialization and deserialization using the <xref:System.Text.Json.JsonSerializerOptions.RespectNullableAnnotations> flag.
501501

502-
The following code shows how to set the option (the `Book` type definition is shown in the previous section):
502+
The following code shows how to set the option:
503503

504504
:::code language="csharp" source="../snippets/dotnet-9/csharp/Serialization.cs" id="RespectNullable":::
505505

506-
> [!NOTE]
507-
> Due to how nullability annotations are represented in IL, the feature is restricted to annotations of non-generic properties.
508-
509-
You can also enable this setting globally using the `System.Text.Json.Serialization.RespectNullableAnnotationsDefault` feature switch in your project file (for example, _.csproj_ file):
510-
511-
```xml
512-
<ItemGroup>
513-
<RuntimeHostConfigurationOption Include="System.Text.Json.Serialization.RespectNullableAnnotationsDefault" Value="true" />
514-
</ItemGroup>
515-
```
516-
517-
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.
506+
For more information, see [Respect nullable annotations](../../../standard/serialization/system-text-json/nullable-annotations.md).
518507

519508
### Require non-optional constructor parameters
520509

@@ -528,15 +517,7 @@ The `MyPoco` type is defined as follows:
528517

529518
:::code language="csharp" source="../snippets/dotnet-9/csharp/Serialization.cs" id="Poco":::
530519

531-
You can also enable this setting globally using the `System.Text.Json.Serialization.RespectRequiredConstructorParametersDefault` feature switch in your project file (for example, _.csproj_ file):
532-
533-
```xml
534-
<ItemGroup>
535-
<RuntimeHostConfigurationOption Include="System.Text.Json.Serialization.RespectRequiredConstructorParametersDefault" Value="true" />
536-
</ItemGroup>
537-
```
538-
539-
As with earlier versions of <xref:System.Text.Json>, you can configure whether individual properties are required using the <xref:System.Text.Json.Serialization.Metadata.JsonPropertyInfo.IsRequired?displayProperty=nameWithType> property.
520+
For more information, see [Non-optional constructor parameters](../../../standard/serialization/system-text-json/required-properties.md#non-optional-constructor-parameters).
540521

541522
### Order JsonObject properties
542523

@@ -562,6 +543,8 @@ enum MyEnum
562543
}
563544
```
564545

546+
For more information, see [Custom enum member names](../../../standard/serialization/system-text-json/customize-properties.md#custom-enum-member-names).
547+
565548
### Stream multiple JSON documents
566549

567550
<xref:System.Text.Json.Utf8JsonReader?displayProperty=nameWithType> now supports reading multiple, whitespace-separated JSON documents from a single buffer or stream. By default, the reader throws an exception if it detects any non-whitespace characters that are trailing the first top-level document. You can change this behavior using the <xref:System.Text.Json.JsonReaderOptions.AllowMultipleValues> flag.

docs/core/whats-new/snippets/dotnet-9/csharp/Serialization.cs

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -64,23 +64,6 @@ public static void RunIt()
6464
// </PropertyOrder>
6565
}
6666

67-
public static void RunIt2()
68-
{
69-
// <RespectNullable>
70-
JsonSerializerOptions options = new() { RespectNullableAnnotations = true };
71-
72-
// Throws exception: System.Text.Json.JsonException: The property or field
73-
// 'Title' on type 'Serialization+Book' doesn't allow getting null values.
74-
// Consider updating its nullability annotation.
75-
JsonSerializer.Serialize(new Book { Title = null! }, options);
76-
77-
// Throws exception: System.Text.Json.JsonException: The property or field
78-
// 'Title' on type 'Serialization+Book' doesn't allow setting null values.
79-
// Consider updating its nullability annotation.
80-
JsonSerializer.Deserialize<Book>("""{ "Title" : null }""", options);
81-
// </RespectNullable>
82-
}
83-
8467
public static void RunIt3()
8568
{
8669
// <RespectRequired>
@@ -105,3 +88,30 @@ public class Book
10588
record MyPoco(string Value);
10689
// </Poco>
10790
}
91+
92+
public static class Serialization2
93+
{
94+
// <RespectNullable>
95+
public static void RunIt()
96+
{
97+
JsonSerializerOptions options = new() { RespectNullableAnnotations = true };
98+
99+
// Throws exception: System.Text.Json.JsonException: The property or field
100+
// 'Title' on type 'Serialization+Book' doesn't allow getting null values.
101+
// Consider updating its nullability annotation.
102+
JsonSerializer.Serialize(new Book { Title = null! }, options);
103+
104+
// Throws exception: System.Text.Json.JsonException: The property or field
105+
// 'Title' on type 'Serialization+Book' doesn't allow setting null values.
106+
// Consider updating its nullability annotation.
107+
JsonSerializer.Deserialize<Book>("""{ "Title" : null }""", options);
108+
}
109+
110+
public class Book
111+
{
112+
public required string Title { get; set; }
113+
public string? Author { get; set; }
114+
public int PublishYear { get; set; }
115+
}
116+
// </RespectNullable>
117+
}

docs/fundamentals/toc.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,8 @@ items:
737737
href: ../standard/serialization/system-text-json/deserialization.md
738738
- name: Require JSON properties
739739
href: ../standard/serialization/system-text-json/required-properties.md
740+
- name: Respect nullable annotations
741+
href: ../standard/serialization/system-text-json/nullable-annotations.md
740742
- name: Allow invalid JSON
741743
href: ../standard/serialization/system-text-json/invalid-json.md
742744
- name: Handle missing members

docs/standard/serialization/system-text-json/customize-properties.md

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: How to customize property names and values with System.Text.Json
33
description: "Learn how to customize property names and values when serializing with System.Text.Json in .NET."
4-
ms.date: 10/16/2023
4+
ms.date: 10/22/2024
55
zone_pivot_groups: dotnet-version
66
no-loc: [System.Text.Json, Newtonsoft.Json]
77
dev_langs:
@@ -21,7 +21,7 @@ ms.custom: vs-copilot-horizontal
2121

2222
By default, property names and dictionary keys are unchanged in the JSON output, including case. Enum values are represented as numbers. And properties are serialized in the order they're defined. However, you can customize these behaviors by:
2323

24-
- Specifying specific serialized property names.
24+
- Specifying specific serialized property and enum member names.
2525
- Using a built-in [naming policy](xref:System.Text.Json.JsonNamingPolicy), such as camelCase, snake_case, or kebab-case, for property names and dictionary keys.
2626
- Using a custom naming policy for property names and dictionary keys.
2727
- Serializing enum values as strings, with or without a naming policy.
@@ -156,18 +156,8 @@ Naming policies for dictionary keys apply to serialization only. If you deserial
156156

157157
## Enums as strings
158158

159-
:::zone pivot="dotnet-8-0"
160-
161159
By default, enums are serialized as numbers. To serialize enum names as strings, use the <xref:System.Text.Json.Serialization.JsonStringEnumConverter> or <xref:System.Text.Json.Serialization.JsonStringEnumConverter%601> converter. Only <xref:System.Text.Json.Serialization.JsonStringEnumConverter%601> is supported by the Native AOT runtime.
162160

163-
:::zone-end
164-
165-
:::zone pivot="dotnet-7-0,dotnet-6-0"
166-
167-
By default, enums are serialized as numbers. To serialize enum names as strings, use the <xref:System.Text.Json.Serialization.JsonStringEnumConverter> converter.
168-
169-
:::zone-end
170-
171161
For example, suppose you need to serialize the following class that has an enum:
172162

173163
:::code language="csharp" source="snippets/how-to/csharp/WeatherForecast.cs" id="WFWithEnum":::
@@ -203,7 +193,7 @@ The built-in <xref:System.Text.Json.Serialization.JsonStringEnumConverter> can d
203193
:::code language="csharp" source="snippets/how-to/csharp/RoundtripEnumAsString.cs" id="Deserialize":::
204194
:::code language="vb" source="snippets/how-to/vb/RoundtripEnumAsString.vb" id="Deserialize":::
205195

206-
:::zone pivot="dotnet-8-0"
196+
### JsonConverterAttribute
207197

208198
You can also specify the converter to use by annotating your enum with <xref:System.Text.Json.Serialization.JsonConverterAttribute>. The following example shows how to specify the <xref:System.Text.Json.Serialization.JsonStringEnumConverter%601> (available in .NET 8 and later versions) by using the <xref:System.Text.Json.Serialization.JsonConverterAttribute> attribute. For example, suppose you need to serialize the following class that has an enum:
209199

@@ -213,7 +203,7 @@ The following sample code serializes the enum names instead of the numeric value
213203

214204
:::code language="csharp" source="snippets/how-to/csharp/RoundtripEnumUsingConverterAttribute.cs" id="Serialize":::
215205

216-
The resulting JSON looks like the following example:
206+
The resulting JSON looks like this:
217207

218208
```json
219209
{
@@ -223,9 +213,31 @@ The resulting JSON looks like the following example:
223213
}
224214
```
225215

226-
To use the converter with source generation, see [Serialize enum fields as strings](source-generation.md#serialize-enum-fields-as-strings).
216+
### Custom enum member names
217+
218+
Starting in .NET 9, you can customize the names of individual enum members for types that are serialized as strings. To customize an enum member name, annotate it with the [JsonStringEnumMemberName attribute](xref:System.Text.Json.Serialization.JsonStringEnumMemberNameAttribute).
219+
220+
For example, suppose you need to serialize the following class that has an enum with a custom member name:
221+
222+
:::code language="csharp" source="snippets/how-to/csharp/WeatherForecast.cs" id="WFWithEnumCustomName":::
223+
224+
The following sample code serializes the enum names instead of the numeric values:
227225

228-
:::zone-end
226+
:::code language="csharp" source="snippets/how-to/csharp/SerializeEnumCustomName.cs" id="Serialize":::
227+
228+
The resulting JSON looks like this:
229+
230+
```json
231+
{
232+
"Date": "2019-08-01T00:00:00-07:00",
233+
"TemperatureCelsius": 25,
234+
"Sky": "Partly cloudy"
235+
}
236+
```
237+
238+
### Source generation
239+
240+
To use the converter with source generation, see [Serialize enum fields as strings](source-generation.md#serialize-enum-fields-as-strings).
229241

230242
## Configure the order of serialized properties
231243

@@ -272,7 +284,7 @@ The following example shows you how to use Copilot to modify existing code to cu
272284
273285
using System.Text.Json;
274286
using System.Text.Json.Serialization;
275-
287+
276288
public class Person
277289
{
278290
[JsonPropertyName("first_name")]
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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+
>
25+
> - 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.
26+
> - The C# compiler uses the [`[NotNull]`](xref:System.Diagnostics.CodeAnalysis.NotNullAttribute), [`[AllowNull]`](xref:System.Diagnostics.CodeAnalysis.AllowNullAttribute), [`[MaybeNull]`](xref:System.Diagnostics.CodeAnalysis.MaybeNullAttribute), and [`[DisallowNull]`](xref:System.Diagnostics.CodeAnalysis.DisallowNullAttribute) attributes to fine-tune annotations in getters and setters. These attributes are also recognized by this System.Text.Json feature. (For more information about the attributes, see [Attributes for null-state static analysis](../../../csharp/language-reference/attributes/nullable-analysis.md).)
27+
28+
## Limitations
29+
30+
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 tries to make up for that by emitting attribute metadata (see [sharplab.io example](https://sharplab.io/#v2:D4AQTAjAsAULBOBTAxge3gEwAQFkCeACqmgBQgQAMWAcqgC7UCuANswMp3wCWAdgOYAaLOQoB+Gi2YBDAEbNEHbvwCUAbiA=)), this metadata is restricted to non-generic member annotations that are scoped to a particular type definition. This limitation 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:
31+
32+
- Top-level types, or the type that's passed when making the first `JsonSerializer.Deserialize()` or `JsonSerializer.Serialize()` call.
33+
- Collection element types&mdash;for example, the `List<string>` and `List<string?>` types are indistinguishable.
34+
- Any properties, fields, or constructor parameters that are generic.
35+
36+
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`.
37+
38+
## Feature switch
39+
40+
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):
41+
42+
```xml
43+
<ItemGroup>
44+
<RuntimeHostConfigurationOption Include="System.Text.Json.Serialization.RespectNullableAnnotationsDefault" Value="true" />
45+
</ItemGroup>
46+
```
47+
48+
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.
49+
50+
## Relationship between nullable and optional parameters
51+
52+
<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:
53+
54+
:::code language="csharp" source="snippets/nullable-annotations/Nullable.cs" id="Unspecified":::
55+
56+
This behavior stems from the C# language itself, where you can have required properties that are nullable:
57+
58+
```csharp
59+
MyPoco poco = new() { Value = null }; // No compiler warnings.
60+
61+
class MyPoco
62+
{
63+
public required string? Value { get; set; }
64+
}
65+
```
66+
67+
And you can also have optional properties that are non-nullable:
68+
69+
```csharp
70+
class MyPoco
71+
{
72+
public string Value { get; set; } = "default";
73+
}
74+
```
75+
76+
The same orthogonality applies to constructor parameters:
77+
78+
```csharp
79+
record MyPoco(
80+
string RequiredNonNullable,
81+
string? RequiredNullable,
82+
string OptionalNonNullable = "default",
83+
string? OptionalNullable = "default"
84+
);
85+
```
86+
87+
## See also
88+
89+
- [Non-optional constructor parameters](required-properties.md#non-optional-constructor-parameters)

0 commit comments

Comments
 (0)