diff --git a/src/libraries/System.Text.Json/Common/JsonSourceGenerationOptionsAttribute.cs b/src/libraries/System.Text.Json/Common/JsonSourceGenerationOptionsAttribute.cs
index 8fd1e3353dc4f..12035eb34c14c 100644
--- a/src/libraries/System.Text.Json/Common/JsonSourceGenerationOptionsAttribute.cs
+++ b/src/libraries/System.Text.Json/Common/JsonSourceGenerationOptionsAttribute.cs
@@ -120,6 +120,11 @@ public JsonSourceGenerationOptionsAttribute(JsonSerializerDefaults defaults)
///
public bool RespectNullableAnnotations { get; set; }
+ ///
+ /// Specifies the default value of when set.
+ ///
+ public bool RespectRequiredConstructorParameters { get; set; }
+
///
/// Specifies the default value of when set.
///
diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
index 62e9fd57d8299..1ed487e6bbd73 100644
--- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
+++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
@@ -1207,6 +1207,9 @@ private static void GetLogicForDefaultSerializerOptionsInit(SourceGenerationOpti
if (optionsSpec.RespectNullableAnnotations is bool respectNullableAnnotations)
writer.WriteLine($"RespectNullableAnnotations = {FormatBoolLiteral(respectNullableAnnotations)},");
+ if (optionsSpec.RespectRequiredConstructorParameters is bool respectRequiredConstructorParameters)
+ writer.WriteLine($"RespectRequiredConstructorParameters = {FormatBoolLiteral(respectRequiredConstructorParameters)},");
+
if (optionsSpec.IgnoreReadOnlyFields is bool ignoreReadOnlyFields)
writer.WriteLine($"IgnoreReadOnlyFields = {FormatBoolLiteral(ignoreReadOnlyFields)},");
diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs
index 66d40939ca423..b32a3f95e929d 100644
--- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs
+++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs
@@ -270,6 +270,7 @@ private SourceGenerationOptionsSpec ParseJsonSourceGenerationOptionsAttribute(IN
JsonKnownNamingPolicy? dictionaryKeyPolicy = null;
bool? respectNullableAnnotations = null;
bool? ignoreReadOnlyFields = null;
+ bool? respectRequiredConstructorParameters = null;
bool? ignoreReadOnlyProperties = null;
bool? includeFields = null;
int? maxDepth = null;
@@ -334,6 +335,10 @@ private SourceGenerationOptionsSpec ParseJsonSourceGenerationOptionsAttribute(IN
respectNullableAnnotations = (bool)namedArg.Value.Value!;
break;
+ case nameof(JsonSourceGenerationOptionsAttribute.RespectRequiredConstructorParameters):
+ respectRequiredConstructorParameters = (bool)namedArg.Value.Value!;
+ break;
+
case nameof(JsonSourceGenerationOptionsAttribute.IgnoreReadOnlyFields):
ignoreReadOnlyFields = (bool)namedArg.Value.Value!;
break;
@@ -418,6 +423,7 @@ private SourceGenerationOptionsSpec ParseJsonSourceGenerationOptionsAttribute(IN
DefaultIgnoreCondition = defaultIgnoreCondition,
DictionaryKeyPolicy = dictionaryKeyPolicy,
RespectNullableAnnotations = respectNullableAnnotations,
+ RespectRequiredConstructorParameters = respectRequiredConstructorParameters,
IgnoreReadOnlyFields = ignoreReadOnlyFields,
IgnoreReadOnlyProperties = ignoreReadOnlyProperties,
IncludeFields = includeFields,
diff --git a/src/libraries/System.Text.Json/gen/Model/SourceGenerationOptionsSpec.cs b/src/libraries/System.Text.Json/gen/Model/SourceGenerationOptionsSpec.cs
index 9b7ecc8ea6920..5c4d250af188f 100644
--- a/src/libraries/System.Text.Json/gen/Model/SourceGenerationOptionsSpec.cs
+++ b/src/libraries/System.Text.Json/gen/Model/SourceGenerationOptionsSpec.cs
@@ -30,6 +30,8 @@ public sealed record SourceGenerationOptionsSpec
public required bool? RespectNullableAnnotations { get; init; }
+ public required bool? RespectRequiredConstructorParameters { get; init; }
+
public required bool? IgnoreReadOnlyFields { get; init; }
public required bool? IgnoreReadOnlyProperties { get; init; }
diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.cs b/src/libraries/System.Text.Json/ref/System.Text.Json.cs
index 6ce961115720b..2dbe6cad3e5eb 100644
--- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs
+++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs
@@ -402,6 +402,7 @@ public JsonSerializerOptions(System.Text.Json.JsonSerializerOptions options) { }
public System.Text.Json.JsonCommentHandling ReadCommentHandling { get { throw null; } set { } }
public System.Text.Json.Serialization.ReferenceHandler? ReferenceHandler { get { throw null; } set { } }
public bool RespectNullableAnnotations { get { throw null; } set { } }
+ public bool RespectRequiredConstructorParameters { get { throw null; } set { } }
public System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver? TypeInfoResolver { get { throw null; } set { } }
public System.Collections.Generic.IList TypeInfoResolverChain { get { throw null; } }
public System.Text.Json.Serialization.JsonUnknownTypeHandling UnknownTypeHandling { get { throw null; } set { } }
@@ -1093,6 +1094,7 @@ public JsonSourceGenerationOptionsAttribute(System.Text.Json.JsonSerializerDefau
public System.Text.Json.Serialization.JsonKnownNamingPolicy PropertyNamingPolicy { get { throw null; } set { } }
public System.Text.Json.JsonCommentHandling ReadCommentHandling { get { throw null; } set { } }
public bool RespectNullableAnnotations { get { throw null; } set { } }
+ public bool RespectRequiredConstructorParameters { get { throw null; } set { } }
public System.Text.Json.Serialization.JsonUnknownTypeHandling UnknownTypeHandling { get { throw null; } set { } }
public System.Text.Json.Serialization.JsonUnmappedMemberHandling UnmappedMemberHandling { get { throw null; } set { } }
public bool UseStringEnumConverter { get { throw null; } set { } }
diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx
index e65049fbb87a0..19ebcbd47137a 100644
--- a/src/libraries/System.Text.Json/src/Resources/Strings.resx
+++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx
@@ -693,7 +693,7 @@
JsonPropertyInfo '{0}' defined in type '{1}' is marked both as required and as an extension data property. This combination is not supported.
- JSON deserialization for type '{0}' was missing required properties, including the following: {1}
+ JSON deserialization for type '{0}' was missing required properties including: {1}.
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.
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/AppContextSwitchHelper.cs b/src/libraries/System.Text.Json/src/System/Text/Json/AppContextSwitchHelper.cs
index f2333a19c315f..01ce7b72e6e9c 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/AppContextSwitchHelper.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/AppContextSwitchHelper.cs
@@ -16,5 +16,11 @@ internal static class AppContextSwitchHelper
switchName: "System.Text.Json.Serialization.RespectNullableAnnotationsDefault",
isEnabled: out bool value)
? value : false;
+
+ public static bool RespectRequiredConstructorParametersDefault { get; } =
+ AppContext.TryGetSwitch(
+ switchName: "System.Text.Json.Serialization.RespectRequiredConstructorParametersDefault",
+ isEnabled: out bool value)
+ ? value : false;
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs
index f8add81e50d9d..a9633d439bcae 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.Caching.cs
@@ -508,6 +508,7 @@ public bool Equals(JsonSerializerOptions? left, JsonSerializerOptions? right)
left._allowOutOfOrderMetadataProperties == right._allowOutOfOrderMetadataProperties &&
left._allowTrailingCommas == right._allowTrailingCommas &&
left._respectNullableAnnotations == right._respectNullableAnnotations &&
+ left._respectRequiredConstructorParameters == right._respectRequiredConstructorParameters &&
left._ignoreNullValues == right._ignoreNullValues &&
left._ignoreReadOnlyProperties == right._ignoreReadOnlyProperties &&
left._ignoreReadonlyFields == right._ignoreReadonlyFields &&
@@ -567,6 +568,7 @@ public int GetHashCode(JsonSerializerOptions options)
AddHashCode(ref hc, options._allowOutOfOrderMetadataProperties);
AddHashCode(ref hc, options._allowTrailingCommas);
AddHashCode(ref hc, options._respectNullableAnnotations);
+ AddHashCode(ref hc, options._respectRequiredConstructorParameters);
AddHashCode(ref hc, options._ignoreNullValues);
AddHashCode(ref hc, options._ignoreReadOnlyProperties);
AddHashCode(ref hc, options._ignoreReadonlyFields);
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs
index acee88788c3a7..1a482bbe24455 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs
@@ -87,6 +87,7 @@ public static JsonSerializerOptions Web
private bool _allowOutOfOrderMetadataProperties;
private bool _allowTrailingCommas;
private bool _respectNullableAnnotations = AppContextSwitchHelper.RespectNullableAnnotationsDefault;
+ private bool _respectRequiredConstructorParameters = AppContextSwitchHelper.RespectRequiredConstructorParametersDefault;
private bool _ignoreNullValues;
private bool _ignoreReadOnlyProperties;
private bool _ignoreReadonlyFields;
@@ -141,6 +142,7 @@ public JsonSerializerOptions(JsonSerializerOptions options)
_allowOutOfOrderMetadataProperties = options._allowOutOfOrderMetadataProperties;
_allowTrailingCommas = options._allowTrailingCommas;
_respectNullableAnnotations = options._respectNullableAnnotations;
+ _respectRequiredConstructorParameters = options._respectRequiredConstructorParameters;
_ignoreNullValues = options._ignoreNullValues;
_ignoreReadOnlyProperties = options._ignoreReadOnlyProperties;
_ignoreReadonlyFields = options._ignoreReadonlyFields;
@@ -797,6 +799,9 @@ public string NewLine
/// Due to restrictions in how nullable reference types are represented at run time,
/// this setting only governs nullability annotations of non-generic properties and fields.
/// It cannot be used to enforce nullability annotations of root-level types or generic parameters.
+ ///
+ /// The default setting for this property can be toggled application-wide using the
+ /// "System.Text.Json.Serialization.RespectNullableAnnotationsDefault" feature switch.
///
public bool RespectNullableAnnotations
{
@@ -808,6 +813,29 @@ public bool RespectNullableAnnotations
}
}
+ ///
+ /// Gets or sets a value that indicates whether non-optional constructor parameters should be specified during deserialization.
+ ///
+ ///
+ /// Thrown if this property is set after serialization or deserialization has occurred.
+ ///
+ ///
+ /// For historical reasons constructor-based deserialization treats all constructor parameters as optional by default.
+ /// This flag allows users to toggle that behavior as necessary for each instance.
+ ///
+ /// The default setting for this property can be toggled application-wide using the
+ /// "System.Text.Json.Serialization.RespectRequiredConstructorParametersDefault" feature switch.
+ ///
+ public bool RespectRequiredConstructorParameters
+ {
+ get => _respectRequiredConstructorParameters;
+ set
+ {
+ VerifyMutable();
+ _respectRequiredConstructorParameters = value;
+ }
+ }
+
///
/// Returns true if options uses compatible built-in resolvers or a combination of compatible built-in resolvers.
///
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonParameterInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonParameterInfo.cs
index 6adb7a7d8fed3..4db70c6694e22 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonParameterInfo.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonParameterInfo.cs
@@ -123,5 +123,9 @@ public ICustomAttributeProvider? AttributeProvider
internal JsonNumberHandling? NumberHandling => MatchingProperty.EffectiveNumberHandling;
internal JsonTypeInfo JsonTypeInfo => MatchingProperty.JsonTypeInfo;
internal bool ShouldDeserialize => !MatchingProperty.IsIgnored;
+ internal bool IsRequiredParameter =>
+ Options.RespectRequiredConstructorParameters &&
+ !HasDefaultValue &&
+ !IsMemberInitializer;
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs
index 6a805e80162bf..cb7975b38c99c 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs
@@ -356,7 +356,7 @@ public bool IsRequired
}
}
- private bool _isRequired;
+ private protected bool _isRequired;
///
/// Gets the constructor parameter associated with the current property.
@@ -448,7 +448,7 @@ internal void Configure()
if (IsRequired)
{
- if (!CanDeserialize)
+ if (!CanDeserialize && AssociatedParameter?.IsRequiredParameter != true)
{
ThrowHelper.ThrowInvalidOperationException_JsonPropertyRequiredAndNotDeserializable(this);
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs
index ff31cf97a353d..a0219eafa1622 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfoOfT.cs
@@ -120,6 +120,8 @@ internal override void AddJsonParameterInfo(JsonParameterInfoValues parameterInf
AssociatedParameter = new JsonParameterInfo(parameterInfoValues, this);
// Overwrite the nullability annotation of property setter with the parameter.
_isSetNullable = parameterInfoValues.IsNullable;
+ // If the property has been associated with a non-optional parameter, mark it as required.
+ _isRequired |= AssociatedParameter.IsRequiredParameter;
}
internal new JsonConverter EffectiveConverter
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs
index 110dcba34fa2c..5b16af8de3eec 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs
@@ -296,7 +296,7 @@ public static void ThrowJsonException_JsonRequiredPropertyMissing(JsonTypeInfo p
Debug.Assert(parent.PropertyCache != null);
// Soft cut-off length - once message becomes longer than that we won't be adding more elements
- const int CutOffLength = 50;
+ const int CutOffLength = 60;
foreach (KeyValuePair kvp in parent.PropertyCache.List)
{
@@ -313,7 +313,9 @@ public static void ThrowJsonException_JsonRequiredPropertyMissing(JsonTypeInfo p
listOfMissingPropertiesBuilder.Append(' ');
}
+ listOfMissingPropertiesBuilder.Append('\'');
listOfMissingPropertiesBuilder.Append(property.Name);
+ listOfMissingPropertiesBuilder.Append('\'');
first = false;
if (listOfMissingPropertiesBuilder.Length >= CutOffLength)
diff --git a/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.ParameterMatching.cs b/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.ParameterMatching.cs
index 2e382dbe75399..e134d42286de9 100644
--- a/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.ParameterMatching.cs
+++ b/src/libraries/System.Text.Json/tests/Common/ConstructorTests/ConstructorTests.ParameterMatching.cs
@@ -12,6 +12,8 @@ namespace System.Text.Json.Serialization.Tests
{
public abstract partial class ConstructorTests : SerializerTests
{
+ private static readonly JsonSerializerOptions s_respectRequiredParamsOptions = new() { RespectRequiredConstructorParameters = true };
+
public ConstructorTests(JsonSerializerWrapper stringSerializer)
: base(stringSerializer)
{
@@ -1636,5 +1638,35 @@ public class CustomCtorParameterConverter : JsonConverter
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
=> writer.WriteStringValue(value);
}
+
+ [Fact]
+ public async Task RespectRequiredConstructorParameters_RequiredParameterMissing_ThrowsJsonException()
+ {
+ string json = """{"X":1,"Z":3}""";
+ JsonException ex = await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(json, s_respectRequiredParamsOptions));
+ Assert.DoesNotContain("'X'", ex.Message);
+ Assert.Contains("'Y'", ex.Message);
+ Assert.DoesNotContain("'Z'", ex.Message);
+ }
+
+ [Fact]
+ public async Task RespectRequiredConstructorParameters_OptionalParameterMissing_Succeeds()
+ {
+ string json = """{"X":1,"Y":2}""";
+ Point_3D result = await Serializer.DeserializeWrapper(json, s_respectRequiredParamsOptions);
+ Assert.Equal(1, result.X);
+ Assert.Equal(2, result.Y);
+ Assert.Equal(50, result.Z);
+ }
+
+ [Fact]
+ public async Task RespectRequiredConstructorParameters_NoParameterMissing_Succeeds()
+ {
+ string json = """{"X":1,"Y":2,"Z":3}""";
+ Point_3D result = await Serializer.DeserializeWrapper(json, s_respectRequiredParamsOptions);
+ Assert.Equal(1, result.X);
+ Assert.Equal(2, result.Y);
+ Assert.Equal(3, result.Z);
+ }
}
}
diff --git a/src/libraries/System.Text.Json/tests/Common/MetadataTests.cs b/src/libraries/System.Text.Json/tests/Common/MetadataTests.cs
index f25c5fe80a5be..967a3f4366a13 100644
--- a/src/libraries/System.Text.Json/tests/Common/MetadataTests.cs
+++ b/src/libraries/System.Text.Json/tests/Common/MetadataTests.cs
@@ -271,6 +271,39 @@ public void JsonTypeInfo_ElementType_ReturnsExpectedValue(Type type, Type? expec
Assert.Equal(expectedKeyType, typeInfo.ElementType);
}
+ [Theory]
+ [InlineData(typeof(ClassWithParameterizedCtor))]
+ [InlineData(typeof(StructWithParameterizedCtor))]
+ [InlineData(typeof(ClassWithRequiredAndOptionalConstructorParameters))]
+ public void RespectRequiredConstructorParameters_false_ReportsCorrespondingPropertiesAsNotRequired(Type type)
+ {
+ var options = new JsonSerializerOptions { RespectRequiredConstructorParameters = false };
+ JsonTypeInfo typeInfo = Serializer.GetTypeInfo(type, options);
+
+ Assert.NotEmpty(typeInfo.Properties);
+ Assert.All(typeInfo.Properties, property =>
+ {
+ Assert.False(property.IsRequired);
+ });
+ }
+
+ [Theory]
+ [InlineData(typeof(ClassWithParameterizedCtor))]
+ [InlineData(typeof(StructWithParameterizedCtor))]
+ [InlineData(typeof(ClassWithRequiredAndOptionalConstructorParameters))]
+ public void RespectRequiredConstructorParameters_true_ReportsCorrespondingPropertiesAsRequired(Type type)
+ {
+ var options = new JsonSerializerOptions { RespectRequiredConstructorParameters = true };
+ JsonTypeInfo typeInfo = Serializer.GetTypeInfo(type, options);
+
+ Assert.NotEmpty(typeInfo.Properties);
+ Assert.All(typeInfo.Properties, property =>
+ {
+ bool isRequiredParam = property.AssociatedParameter is { HasDefaultValue: false, IsMemberInitializer: false };
+ Assert.Equal(isRequiredParam, property.IsRequired);
+ });
+ }
+
private static object? GetDefaultValue(ParameterInfo parameterInfo)
{
Type parameterType = parameterInfo.ParameterType;
@@ -506,6 +539,18 @@ public sealed class CustomConverter : JsonConverter throw new NotImplementedException();
}
}
+
+ internal class ClassWithRequiredAndOptionalConstructorParameters
+ {
+ [JsonConstructor]
+ public ClassWithRequiredAndOptionalConstructorParameters(string? x, string? y = null)
+ {
+ X = x;
+ Y = y;
+ }
+ public string? X { get; }
+ public string? Y { get; }
+ }
}
internal class WeatherForecastWithPOCOs
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/MetadataTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/MetadataTests.cs
index 2dee7e6da78f4..f7030eb7c45e9 100644
--- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/MetadataTests.cs
+++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/MetadataTests.cs
@@ -39,6 +39,7 @@ public partial class MetadataTests_SourceGen() : MetadataTests(new StringSeriali
[JsonSerializable(typeof(ClassWithMultipleConstructors))]
[JsonSerializable(typeof(DerivedClassWithShadowingProperties))]
[JsonSerializable(typeof(IDerivedInterface))]
+ [JsonSerializable(typeof(ClassWithRequiredAndOptionalConstructorParameters))]
partial class Context : JsonSerializerContext;
}
}
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CacheTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CacheTests.cs
index 739be45931b9e..5a75a784cd3e4 100644
--- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CacheTests.cs
+++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/CacheTests.cs
@@ -374,6 +374,7 @@ public static void JsonSerializerOptions_EqualityComparer_ChangingAnySettingShou
yield return (GetProp(nameof(JsonSerializerOptions.UnknownTypeHandling)), JsonUnknownTypeHandling.JsonNode);
yield return (GetProp(nameof(JsonSerializerOptions.WriteIndented)), true);
yield return (GetProp(nameof(JsonSerializerOptions.RespectNullableAnnotations)), !JsonSerializerOptions.Default.RespectNullableAnnotations);
+ yield return (GetProp(nameof(JsonSerializerOptions.RespectRequiredConstructorParameters)), !JsonSerializerOptions.Default.RespectRequiredConstructorParameters);
yield return (GetProp(nameof(JsonSerializerOptions.IndentCharacter)), '\t');
yield return (GetProp(nameof(JsonSerializerOptions.IndentSize)), 1);
yield return (GetProp(nameof(JsonSerializerOptions.ReferenceHandler)), ReferenceHandler.Preserve);
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/OptionsTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/OptionsTests.cs
index f5ef9964d8f1c..f0b9b5ad0ca63 100644
--- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/OptionsTests.cs
+++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/OptionsTests.cs
@@ -67,6 +67,7 @@ public static void SetOptionsFail()
Assert.Equal(JsonUnmappedMemberHandling.Skip, options.UnmappedMemberHandling);
Assert.False(options.WriteIndented);
Assert.False(options.RespectNullableAnnotations);
+ Assert.False(options.RespectRequiredConstructorParameters);
TestIListNonThrowingOperationsWhenImmutable(options.Converters, tc);
TestIListNonThrowingOperationsWhenImmutable(options.TypeInfoResolverChain, options.TypeInfoResolver);
@@ -87,6 +88,7 @@ public static void SetOptionsFail()
Assert.Throws(() => options.WriteIndented = options.WriteIndented);
Assert.Throws(() => options.TypeInfoResolver = options.TypeInfoResolver);
Assert.Throws(() => options.RespectNullableAnnotations = options.RespectNullableAnnotations);
+ Assert.Throws(() => options.RespectRequiredConstructorParameters = options.RespectRequiredConstructorParameters);
TestIListThrowingOperationsWhenImmutable(options.Converters, tc);
TestIListThrowingOperationsWhenImmutable(options.TypeInfoResolverChain, options.TypeInfoResolver);
@@ -986,6 +988,46 @@ public static void Options_NullabilityInfoFeatureSwitchDisabled_ReportsPropertie
}, options).Dispose();
}
+ [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)]
+ [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+ [InlineData(null)]
+ [InlineData(false)]
+ [InlineData(true)]
+ public static void Options_RespectRequiredConstructorParameters_FeatureSwitch(bool? state)
+ {
+ var options = new RemoteInvokeOptions();
+ if (state.HasValue)
+ {
+ options.RuntimeConfigurationOptions["System.Text.Json.Serialization.RespectRequiredConstructorParametersDefault"] = state.Value;
+ }
+
+ string arg = state ?? false ? "true" : "false";
+ RemoteExecutor.Invoke(static arg =>
+ {
+ bool shouldRespectRequiredConstructorParameters = bool.Parse(arg);
+
+ var jsonOptions = new JsonSerializerOptions();
+ Assert.Equal(shouldRespectRequiredConstructorParameters, jsonOptions.RespectRequiredConstructorParameters);
+ Assert.Equal(shouldRespectRequiredConstructorParameters, JsonSerializerOptions.Default.RespectRequiredConstructorParameters);
+
+ string json = """{"X":1,"Z":3}""";
+
+ if (shouldRespectRequiredConstructorParameters)
+ {
+ JsonException ex = Assert.Throws(() => JsonSerializer.Deserialize(json));
+ Assert.Contains("'Y'", ex.Message);
+ }
+ else
+ {
+ Point_3D result = JsonSerializer.Deserialize(json);
+ Assert.Equal(1, result.X);
+ Assert.Equal(0, result.Y);
+ Assert.Equal(3, result.Z);
+ }
+
+ }, arg, options).Dispose();
+ }
+
[SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)]
[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
public static void Options_NullabilityInfoFeatureSwitchDisabled_RespectNullabilityAnnotationsEnabled_ThrowsInvalidOperationException()