Skip to content

Commit 2823751

Browse files
eiriktsarpalispull[bot]
authored andcommitted
Implement JsonSerializerOptions.RespectRequiredConstructorParameters (#103096)
* Implement JsonSerializerOptions.RespectRequiredConstructorParameters * Update src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.ParameterMatching.cs * Update src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.ParameterMatching.cs * Update src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/OptionsTests.cs
1 parent 89cca44 commit 2823751

File tree

18 files changed

+187
-4
lines changed

18 files changed

+187
-4
lines changed

src/libraries/System.Text.Json/Common/JsonSourceGenerationOptionsAttribute.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ public JsonSourceGenerationOptionsAttribute(JsonSerializerDefaults defaults)
120120
/// </summary>
121121
public bool RespectNullableAnnotations { get; set; }
122122

123+
/// <summary>
124+
/// Specifies the default value of <see cref="JsonSerializerOptions.RespectRequiredConstructorParameters"/> when set.
125+
/// </summary>
126+
public bool RespectRequiredConstructorParameters { get; set; }
127+
123128
/// <summary>
124129
/// Specifies the default value of <see cref="JsonSerializerOptions.UnknownTypeHandling"/> when set.
125130
/// </summary>

src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,6 +1207,9 @@ private static void GetLogicForDefaultSerializerOptionsInit(SourceGenerationOpti
12071207
if (optionsSpec.RespectNullableAnnotations is bool respectNullableAnnotations)
12081208
writer.WriteLine($"RespectNullableAnnotations = {FormatBoolLiteral(respectNullableAnnotations)},");
12091209

1210+
if (optionsSpec.RespectRequiredConstructorParameters is bool respectRequiredConstructorParameters)
1211+
writer.WriteLine($"RespectRequiredConstructorParameters = {FormatBoolLiteral(respectRequiredConstructorParameters)},");
1212+
12101213
if (optionsSpec.IgnoreReadOnlyFields is bool ignoreReadOnlyFields)
12111214
writer.WriteLine($"IgnoreReadOnlyFields = {FormatBoolLiteral(ignoreReadOnlyFields)},");
12121215

src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ private SourceGenerationOptionsSpec ParseJsonSourceGenerationOptionsAttribute(IN
270270
JsonKnownNamingPolicy? dictionaryKeyPolicy = null;
271271
bool? respectNullableAnnotations = null;
272272
bool? ignoreReadOnlyFields = null;
273+
bool? respectRequiredConstructorParameters = null;
273274
bool? ignoreReadOnlyProperties = null;
274275
bool? includeFields = null;
275276
int? maxDepth = null;
@@ -334,6 +335,10 @@ private SourceGenerationOptionsSpec ParseJsonSourceGenerationOptionsAttribute(IN
334335
respectNullableAnnotations = (bool)namedArg.Value.Value!;
335336
break;
336337

338+
case nameof(JsonSourceGenerationOptionsAttribute.RespectRequiredConstructorParameters):
339+
respectRequiredConstructorParameters = (bool)namedArg.Value.Value!;
340+
break;
341+
337342
case nameof(JsonSourceGenerationOptionsAttribute.IgnoreReadOnlyFields):
338343
ignoreReadOnlyFields = (bool)namedArg.Value.Value!;
339344
break;
@@ -418,6 +423,7 @@ private SourceGenerationOptionsSpec ParseJsonSourceGenerationOptionsAttribute(IN
418423
DefaultIgnoreCondition = defaultIgnoreCondition,
419424
DictionaryKeyPolicy = dictionaryKeyPolicy,
420425
RespectNullableAnnotations = respectNullableAnnotations,
426+
RespectRequiredConstructorParameters = respectRequiredConstructorParameters,
421427
IgnoreReadOnlyFields = ignoreReadOnlyFields,
422428
IgnoreReadOnlyProperties = ignoreReadOnlyProperties,
423429
IncludeFields = includeFields,

src/libraries/System.Text.Json/gen/Model/SourceGenerationOptionsSpec.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ public sealed record SourceGenerationOptionsSpec
3030

3131
public required bool? RespectNullableAnnotations { get; init; }
3232

33+
public required bool? RespectRequiredConstructorParameters { get; init; }
34+
3335
public required bool? IgnoreReadOnlyFields { get; init; }
3436

3537
public required bool? IgnoreReadOnlyProperties { get; init; }

src/libraries/System.Text.Json/ref/System.Text.Json.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,7 @@ public JsonSerializerOptions(System.Text.Json.JsonSerializerOptions options) { }
402402
public System.Text.Json.JsonCommentHandling ReadCommentHandling { get { throw null; } set { } }
403403
public System.Text.Json.Serialization.ReferenceHandler? ReferenceHandler { get { throw null; } set { } }
404404
public bool RespectNullableAnnotations { get { throw null; } set { } }
405+
public bool RespectRequiredConstructorParameters { get { throw null; } set { } }
405406
public System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver? TypeInfoResolver { get { throw null; } set { } }
406407
public System.Collections.Generic.IList<System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver> TypeInfoResolverChain { get { throw null; } }
407408
public System.Text.Json.Serialization.JsonUnknownTypeHandling UnknownTypeHandling { get { throw null; } set { } }
@@ -1093,6 +1094,7 @@ public JsonSourceGenerationOptionsAttribute(System.Text.Json.JsonSerializerDefau
10931094
public System.Text.Json.Serialization.JsonKnownNamingPolicy PropertyNamingPolicy { get { throw null; } set { } }
10941095
public System.Text.Json.JsonCommentHandling ReadCommentHandling { get { throw null; } set { } }
10951096
public bool RespectNullableAnnotations { get { throw null; } set { } }
1097+
public bool RespectRequiredConstructorParameters { get { throw null; } set { } }
10961098
public System.Text.Json.Serialization.JsonUnknownTypeHandling UnknownTypeHandling { get { throw null; } set { } }
10971099
public System.Text.Json.Serialization.JsonUnmappedMemberHandling UnmappedMemberHandling { get { throw null; } set { } }
10981100
public bool UseStringEnumConverter { get { throw null; } set { } }

src/libraries/System.Text.Json/src/Resources/Strings.resx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -693,7 +693,7 @@
693693
<value>JsonPropertyInfo '{0}' defined in type '{1}' is marked both as required and as an extension data property. This combination is not supported.</value>
694694
</data>
695695
<data name="JsonRequiredPropertiesMissing" xml:space="preserve">
696-
<value>JSON deserialization for type '{0}' was missing required properties, including the following: {1}</value>
696+
<value>JSON deserialization for type '{0}' was missing required properties including: {1}.</value>
697697
</data>
698698
<data name="ObjectCreationHandlingPopulateNotSupportedByConverter" xml:space="preserve">
699699
<value>Property '{0}' on type '{1}' is marked with JsonObjectCreationHandling.Populate but it doesn't support populating. This can be either because the property type is immutable or it could use a custom converter.</value>

src/libraries/System.Text.Json/src/System/Text/Json/AppContextSwitchHelper.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,11 @@ internal static class AppContextSwitchHelper
1616
switchName: "System.Text.Json.Serialization.RespectNullableAnnotationsDefault",
1717
isEnabled: out bool value)
1818
? value : false;
19+
20+
public static bool RespectRequiredConstructorParametersDefault { get; } =
21+
AppContext.TryGetSwitch(
22+
switchName: "System.Text.Json.Serialization.RespectRequiredConstructorParametersDefault",
23+
isEnabled: out bool value)
24+
? value : false;
1925
}
2026
}

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,7 @@ public bool Equals(JsonSerializerOptions? left, JsonSerializerOptions? right)
508508
left._allowOutOfOrderMetadataProperties == right._allowOutOfOrderMetadataProperties &&
509509
left._allowTrailingCommas == right._allowTrailingCommas &&
510510
left._respectNullableAnnotations == right._respectNullableAnnotations &&
511+
left._respectRequiredConstructorParameters == right._respectRequiredConstructorParameters &&
511512
left._ignoreNullValues == right._ignoreNullValues &&
512513
left._ignoreReadOnlyProperties == right._ignoreReadOnlyProperties &&
513514
left._ignoreReadonlyFields == right._ignoreReadonlyFields &&
@@ -567,6 +568,7 @@ public int GetHashCode(JsonSerializerOptions options)
567568
AddHashCode(ref hc, options._allowOutOfOrderMetadataProperties);
568569
AddHashCode(ref hc, options._allowTrailingCommas);
569570
AddHashCode(ref hc, options._respectNullableAnnotations);
571+
AddHashCode(ref hc, options._respectRequiredConstructorParameters);
570572
AddHashCode(ref hc, options._ignoreNullValues);
571573
AddHashCode(ref hc, options._ignoreReadOnlyProperties);
572574
AddHashCode(ref hc, options._ignoreReadonlyFields);

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ public static JsonSerializerOptions Web
8787
private bool _allowOutOfOrderMetadataProperties;
8888
private bool _allowTrailingCommas;
8989
private bool _respectNullableAnnotations = AppContextSwitchHelper.RespectNullableAnnotationsDefault;
90+
private bool _respectRequiredConstructorParameters = AppContextSwitchHelper.RespectRequiredConstructorParametersDefault;
9091
private bool _ignoreNullValues;
9192
private bool _ignoreReadOnlyProperties;
9293
private bool _ignoreReadonlyFields;
@@ -141,6 +142,7 @@ public JsonSerializerOptions(JsonSerializerOptions options)
141142
_allowOutOfOrderMetadataProperties = options._allowOutOfOrderMetadataProperties;
142143
_allowTrailingCommas = options._allowTrailingCommas;
143144
_respectNullableAnnotations = options._respectNullableAnnotations;
145+
_respectRequiredConstructorParameters = options._respectRequiredConstructorParameters;
144146
_ignoreNullValues = options._ignoreNullValues;
145147
_ignoreReadOnlyProperties = options._ignoreReadOnlyProperties;
146148
_ignoreReadonlyFields = options._ignoreReadonlyFields;
@@ -797,6 +799,9 @@ public string NewLine
797799
/// Due to restrictions in how nullable reference types are represented at run time,
798800
/// this setting only governs nullability annotations of non-generic properties and fields.
799801
/// It cannot be used to enforce nullability annotations of root-level types or generic parameters.
802+
///
803+
/// The default setting for this property can be toggled application-wide using the
804+
/// "System.Text.Json.Serialization.RespectNullableAnnotationsDefault" feature switch.
800805
/// </remarks>
801806
public bool RespectNullableAnnotations
802807
{
@@ -808,6 +813,29 @@ public bool RespectNullableAnnotations
808813
}
809814
}
810815

816+
/// <summary>
817+
/// Gets or sets a value that indicates whether non-optional constructor parameters should be specified during deserialization.
818+
/// </summary>
819+
/// <exception cref="InvalidOperationException">
820+
/// Thrown if this property is set after serialization or deserialization has occurred.
821+
/// </exception>
822+
/// <remarks>
823+
/// For historical reasons constructor-based deserialization treats all constructor parameters as optional by default.
824+
/// This flag allows users to toggle that behavior as necessary for each <see cref="JsonSerializerOptions"/> instance.
825+
///
826+
/// The default setting for this property can be toggled application-wide using the
827+
/// "System.Text.Json.Serialization.RespectRequiredConstructorParametersDefault" feature switch.
828+
/// </remarks>
829+
public bool RespectRequiredConstructorParameters
830+
{
831+
get => _respectRequiredConstructorParameters;
832+
set
833+
{
834+
VerifyMutable();
835+
_respectRequiredConstructorParameters = value;
836+
}
837+
}
838+
811839
/// <summary>
812840
/// Returns true if options uses compatible built-in resolvers or a combination of compatible built-in resolvers.
813841
/// </summary>

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonParameterInfo.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,5 +123,9 @@ public ICustomAttributeProvider? AttributeProvider
123123
internal JsonNumberHandling? NumberHandling => MatchingProperty.EffectiveNumberHandling;
124124
internal JsonTypeInfo JsonTypeInfo => MatchingProperty.JsonTypeInfo;
125125
internal bool ShouldDeserialize => !MatchingProperty.IsIgnored;
126+
internal bool IsRequiredParameter =>
127+
Options.RespectRequiredConstructorParameters &&
128+
!HasDefaultValue &&
129+
!IsMemberInitializer;
126130
}
127131
}

0 commit comments

Comments
 (0)