Skip to content

Commit 8e12dee

Browse files
committed
non-optional constructor params
1 parent a824b72 commit 8e12dee

File tree

5 files changed

+144
-61
lines changed

5 files changed

+144
-61
lines changed

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

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -528,15 +528,7 @@ The `MyPoco` type is defined as follows:
528528

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

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.
531+
For more information, see [Non-optional constructor parameters](../../../standard/serialization/system-text-json/required-properties.md#non-optional-constructor-parameters).
540532

541533
### Order JsonObject properties
542534

Lines changed: 34 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,62 @@
11
---
22
title: Require properties for deserialization
33
description: "Learn how to mark properties as required for deserialization to succeed."
4-
ms.date: 09/21/2022
4+
ms.date: 10/22/2024
55
no-loc: [System.Text.Json, Newtonsoft.Json]
66
---
77
# Required properties
88

9-
Starting in .NET 7, you can mark certain properties to signify that they must be present in the JSON payload for deserialization to succeed. If one or more of these required properties is not present, the <xref:System.Text.Json.JsonSerializer.Deserialize%2A?displayProperty=nameWithType> methods throw a <xref:System.Text.Json.JsonException>.
9+
You can mark certain properties to signify that they must be present in the JSON payload for deserialization to succeed. Similarly, you can set an option to specify that all non-optional constructor parameters are present in the JSON payload. If one or more of these required properties is not present, the <xref:System.Text.Json.JsonSerializer.Deserialize%2A?displayProperty=nameWithType> methods throw a <xref:System.Text.Json.JsonException>.
1010

1111
There are three ways to mark a property or field as required for JSON deserialization:
1212

13-
- By adding the [required modifier](../../../csharp/language-reference/keywords/required.md), which is new in C# 11.
14-
- By annotating it with <xref:System.Text.Json.Serialization.JsonRequiredAttribute>, which is new in .NET 7.
15-
- By modifying the <xref:System.Text.Json.Serialization.Metadata.JsonPropertyInfo.IsRequired?displayProperty=nameWithType> property of the contract model, which is new in .NET 7.
13+
- By adding the [`required` modifier](../../../csharp/language-reference/keywords/required.md).
14+
- By annotating it with <xref:System.Text.Json.Serialization.JsonRequiredAttribute>.
15+
- By modifying the <xref:System.Text.Json.Serialization.Metadata.JsonPropertyInfo.IsRequired?displayProperty=nameWithType> property of the contract model.
1616

17-
From the serializer's perspective, these two demarcations are equivalent and both map to the same piece of metadata, which is <xref:System.Text.Json.Serialization.Metadata.JsonPropertyInfo.IsRequired?displayProperty=nameWithType>. In most cases, you'd simply use the built-in C# keyword. However, in the following cases, you should use <xref:System.Text.Json.Serialization.JsonRequiredAttribute> instead:
17+
To specify that all non-optional constructor parameters are required for JSON deserialization, set the <xref:System.Text.Json.JsonSerializerOptions.RespectRequiredConstructorParameters?displayProperty=nameWithType> option (or, for source generation, <xref:System.Text.Json.Serialization.JsonSourceGenerationOptionsAttribute.RespectRequiredConstructorParameters> property) to `true`. For more information, see the [Non-optional constructor parameters](#non-optional-constructor-parameters) section.
18+
19+
From the serializer's perspective, the C# `required` modifier and [`[JsonRequired]`](xref:System.Text.Json.Serialization.JsonRequiredAttribute) attribute are equivalent, and both map to the same piece of metadata, which is <xref:System.Text.Json.Serialization.Metadata.JsonPropertyInfo.IsRequired?displayProperty=nameWithType>. In most cases, you'd simply use the built-in C# keyword. However, in the following cases, you should use <xref:System.Text.Json.Serialization.JsonRequiredAttribute> instead:
1820

1921
- If you're using a programming language other than C# or a down-level version of C#.
2022
- If you only want the requirement to apply to JSON deserialization.
2123
- If you're using `System.Text.Json` serialization in [source generation](source-generation-modes.md#metadata-based-mode) mode. In this case, your code won't compile if you use the `required` modifier, as source generation occurs at compile time.
2224

2325
The following code snippet shows an example of a property modified with the `required` keyword. This property must be present in the JSON payload for deserialization to succeed.
2426

25-
```csharp
26-
using System.Text.Json;
27+
:::code language="csharp" source="snippets/required-properties/RequiredProperties.cs" id="Keyword":::
2728

28-
// The following line throws a JsonException at run time.
29-
Console.WriteLine(JsonSerializer.Deserialize<Person>("""{"Age": 42}"""));
29+
Alternatively, you can use <xref:System.Text.Json.Serialization.JsonRequiredAttribute>:
3030

31-
public class Person
32-
{
33-
public required string Name { get; set; }
34-
public int Age { get; set; }
35-
}
36-
```
31+
:::code language="csharp" source="snippets/required-properties/RequiredProperties.cs" id="Attribute":::
3732

38-
Alternatively, you can use <xref:System.Text.Json.Serialization.JsonRequiredAttribute>:
33+
It's also possible to control whether a property is required via the contract model using the <xref:System.Text.Json.Serialization.Metadata.JsonPropertyInfo.IsRequired?displayProperty=nameWithType> property:
3934

40-
```csharp
41-
using System.Text.Json;
35+
:::code language="csharp" source="snippets/required-properties/RequiredProperties.cs" id="ContractModel":::
36+
37+
## Non-optional constructor parameters
4238

43-
// The following line throws a JsonException at run time.
44-
Console.WriteLine(JsonSerializer.Deserialize<Person>("""{"Age": 42}"""));
39+
Prior to .NET 9, constructor-based deserialization treated all constructor parameters as optional, as the following example shows:
4540

46-
public class Person
47-
{
48-
[JsonRequired]
49-
public string Name { get; set; }
50-
public int Age { get; set; }
51-
}
41+
```csharp
42+
var result = JsonSerializer.Deserialize<Person>("{}");
43+
Console.WriteLine(result); // Person { Name = , Age = 0 }
44+
45+
record Person(string Name, int Age);
5246
```
5347

54-
It's also possible to control whether a property is required via the contract model using the <xref:System.Text.Json.Serialization.Metadata.JsonPropertyInfo.IsRequired?displayProperty=nameWithType> property:
48+
Starting in .NET 9, you can set the <xref:System.Text.Json.JsonSerializerOptions.RespectRequiredConstructorParameters> flag to treat non-optional constructor parameters as required.
5549

56-
```csharp
57-
var options = new JsonSerializerOptions
58-
{
59-
TypeInfoResolver = new DefaultJsonTypeInfoResolver
60-
{
61-
Modifiers =
62-
{
63-
static typeInfo =>
64-
{
65-
if (typeInfo.Kind != JsonTypeInfoKind.Object)
66-
return;
67-
68-
foreach (JsonPropertyInfo propertyInfo in typeInfo.Properties)
69-
{
70-
// Strip IsRequired constraint from every property.
71-
propertyInfo.IsRequired = false;
72-
}
73-
}
74-
}
75-
}
76-
};
77-
78-
// Deserialization now succeeds even though the Name property isn't in the JSON payload.
79-
JsonSerializer.Deserialize<Person>("""{"Age": 42}""", options);
50+
:::code language="csharp" source="snippets/required-properties/RequiredProperties.cs" id="RequiredConstrParams":::
51+
52+
### Feature switch
53+
54+
You can turn on the `RespectRequiredConstructorParameters` setting globally using the `System.Text.Json.Serialization.RespectRequiredConstructorParametersDefault` feature switch. Add the following MSBuild item to your project file (for example, *.csproj* file):
55+
56+
```xml
57+
<ItemGroup>
58+
<RuntimeHostConfigurationOption Include="System.Text.Json.Serialization.RespectRequiredConstructorParametersDefault" Value="true" />
59+
</ItemGroup>
8060
```
61+
62+
The `RespectRequiredConstructorParametersDefault` 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.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
//RequiredProperties.RunIt();
2+
//RequiredProperties2.RunIt();
3+
//RequiredProperties3.RunIt();
4+
RequiredProperties4.RunIt();
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net9.0</TargetFramework>
6+
<Nullable>enable</Nullable>
7+
<ImplicitUsings>enable</ImplicitUsings>
8+
</PropertyGroup>
9+
</Project>
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
using System.Text.Json;
2+
using System.Text.Json.Serialization;
3+
using System.Text.Json.Serialization.Metadata;
4+
5+
public static class RequiredProperties
6+
{
7+
// <Keyword>
8+
public static void RunIt()
9+
{
10+
// The following line throws a JsonException at run time.
11+
Console.WriteLine(JsonSerializer.Deserialize<Person>("""{"Age": 42}"""));
12+
}
13+
14+
public class Person
15+
{
16+
public required string Name { get; set; }
17+
public int Age { get; set; }
18+
}
19+
// </Keyword>
20+
}
21+
22+
public static class RequiredProperties2
23+
{
24+
// <Attribute>
25+
public static void RunIt()
26+
{
27+
// The following line throws a JsonException at run time.
28+
Console.WriteLine(JsonSerializer.Deserialize<Person>("""{"Age": 42}"""));
29+
}
30+
31+
public class Person
32+
{
33+
[JsonRequired]
34+
public string Name { get; set; }
35+
public int Age { get; set; }
36+
}
37+
// </Attribute>
38+
}
39+
40+
public static class RequiredProperties3
41+
{
42+
// <ContractModel>
43+
public static void RunIt()
44+
{
45+
var options = new JsonSerializerOptions
46+
{
47+
TypeInfoResolver = new DefaultJsonTypeInfoResolver
48+
{
49+
Modifiers =
50+
{
51+
static typeInfo =>
52+
{
53+
if (typeInfo.Kind != JsonTypeInfoKind.Object)
54+
return;
55+
56+
foreach (JsonPropertyInfo propertyInfo in typeInfo.Properties)
57+
{
58+
// Strip IsRequired constraint from every property.
59+
propertyInfo.IsRequired = false;
60+
}
61+
}
62+
}
63+
}
64+
};
65+
66+
// Deserialization succeeds even though
67+
// the Name property isn't in the JSON payload.
68+
JsonSerializer.Deserialize<Person>("""{"Age": 42}""", options);
69+
}
70+
71+
public class Person
72+
{
73+
public required string Name { get; set; }
74+
public int Age { get; set; }
75+
}
76+
// </ContractModel>
77+
}
78+
79+
public static class RequiredProperties4
80+
{
81+
// <RequiredConstrParams>
82+
public static void RunIt()
83+
{
84+
JsonSerializerOptions options = new()
85+
{
86+
RespectRequiredConstructorParameters = true
87+
};
88+
string json = """{"Age": 42}""";
89+
90+
// The following line throws a JsonException at run time.
91+
JsonSerializer.Deserialize<Person>(json, options);
92+
}
93+
94+
record Person(string Name, int? Age = null);
95+
// </RequiredConstrParams>
96+
}

0 commit comments

Comments
 (0)