diff --git a/src/libraries/System.Text.Json/Common/JsonSerializerDefaults.cs b/src/libraries/System.Text.Json/Common/JsonSerializerDefaults.cs
index fe762976f8a05d..7f7b609865badd 100644
--- a/src/libraries/System.Text.Json/Common/JsonSerializerDefaults.cs
+++ b/src/libraries/System.Text.Json/Common/JsonSerializerDefaults.cs
@@ -15,12 +15,28 @@ public enum JsonSerializerDefaults
/// This option implies that property names are treated as case-sensitive and that "PascalCase" name formatting should be employed.
///
General = 0,
+
///
/// Specifies that values should be used more appropriate to web-based scenarios.
///
///
/// This option implies that property names are treated as case-insensitive and that "camelCase" name formatting should be employed.
///
- Web = 1
+ Web = 1,
+
+ ///
+ /// Specifies that stricter policies should be applied when deserializing from JSON.
+ ///
+ ///
+ /// JSON produced with can be deserialized with .
+ /// The following policies are used:
+ ///
+ /// - Property names are treated as case-sensitive and "PascalCase" name formatting is employed.
+ /// - Properties that cannot be mapped to a .NET member are not allowed.
+ /// - Properties with duplicate names are not allowed.
+ /// - Nullable annotations and required constructor parameters are respected.
+ ///
+ ///
+ Strict = 2,
}
}
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 616934dc9ac456..04378981084d2c 100644
--- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs
+++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs
@@ -388,6 +388,7 @@ public enum JsonSerializerDefaults
{
General = 0,
Web = 1,
+ Strict = 2,
}
public sealed partial class JsonSerializerOptions
{
@@ -421,6 +422,7 @@ public JsonSerializerOptions(System.Text.Json.JsonSerializerOptions options) { }
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 static System.Text.Json.JsonSerializerOptions Strict { [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed and might need runtime code generation. Use System.Text.Json source generation for native AOT applications."), System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("JSON serialization and deserialization might require types that cannot be statically analyzed. Use the overload that takes a JsonTypeInfo or JsonSerializerContext, or make sure all of the required types are preserved.")] get { throw null; } }
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 { } }
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 01e426eb1aaaea..00f913e068ee38 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
@@ -39,14 +39,9 @@ public static JsonSerializerOptions Default
{
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
- get
- {
- return s_defaultOptions ?? GetOrCreateSingleton(ref s_defaultOptions, JsonSerializerDefaults.General);
- }
+ get => field ?? GetOrCreateSingleton(ref field, JsonSerializerDefaults.General);
}
- private static JsonSerializerOptions? s_defaultOptions;
-
///
/// Gets a read-only, singleton instance of that uses the web configuration.
///
@@ -59,13 +54,23 @@ public static JsonSerializerOptions Web
{
[RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
[RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
- get
- {
- return s_webOptions ?? GetOrCreateSingleton(ref s_webOptions, JsonSerializerDefaults.Web);
- }
+ get => field ?? GetOrCreateSingleton(ref field, JsonSerializerDefaults.Web);
}
- private static JsonSerializerOptions? s_webOptions;
+ ///
+ /// Gets a read-only, singleton instance of that uses the strict configuration.
+ ///
+ ///
+ /// Each instance encapsulates its own serialization metadata caches,
+ /// so using fresh default instances every time one is needed can result in redundant recomputation of converters.
+ /// This property provides a shared instance that can be consumed by any number of components without necessitating any converter recomputation.
+ ///
+ public static JsonSerializerOptions Strict
+ {
+ [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)]
+ [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)]
+ get => field ?? GetOrCreateSingleton(ref field, JsonSerializerDefaults.Strict);
+ }
// For any new option added, consider adding it to the options copied in the copy constructor below
// and consider updating the EqualtyComparer used for comparing CachingContexts.
@@ -172,6 +177,13 @@ public JsonSerializerOptions(JsonSerializerDefaults defaults) : this()
_jsonPropertyNamingPolicy = JsonNamingPolicy.CamelCase;
_numberHandling = JsonNumberHandling.AllowReadingFromString;
}
+ else if (defaults == JsonSerializerDefaults.Strict)
+ {
+ _unmappedMemberHandling = JsonUnmappedMemberHandling.Disallow;
+ _allowDuplicateProperties = false;
+ _respectNullableAnnotations = true;
+ _respectRequiredConstructorParameters = true;
+ }
else if (defaults != JsonSerializerDefaults.General)
{
throw new ArgumentOutOfRangeException(nameof(defaults));
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSourceGenerationOptionsTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSourceGenerationOptionsTests.cs
index 17f5e125a84c1b..5c4cc405d9c463 100644
--- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSourceGenerationOptionsTests.cs
+++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/JsonSourceGenerationOptionsTests.cs
@@ -37,6 +37,20 @@ public static void ContextWithWebSerializerDefaults_GeneratesExpectedOptions()
public partial class ContextWithWebSerializerDefaults : JsonSerializerContext
{ }
+ [Fact]
+ public static void ContextWithStrictSerializerDefaults_GeneratesExpectedOptions()
+ {
+ JsonSerializerOptions expected = new(JsonSerializerDefaults.Strict) { TypeInfoResolver = ContextWithStrictSerializerDefaults.Default };
+ JsonSerializerOptions options = ContextWithStrictSerializerDefaults.Default.Options;
+
+ JsonTestHelper.AssertOptionsEqual(expected, options);
+ }
+
+ [JsonSourceGenerationOptions(JsonSerializerDefaults.Strict)]
+ [JsonSerializable(typeof(PersonStruct))]
+ public partial class ContextWithStrictSerializerDefaults : JsonSerializerContext
+ { }
+
[Fact]
public static void ContextWithWebDefaultsAndOverriddenPropertyNamingPolicy_GeneratesExpectedOptions()
{
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 840a5e1079e50f..17141dabfd8771 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
@@ -1302,6 +1302,54 @@ public static void JsonSerializerOptions_Web_IsReadOnly()
Assert.Throws(() => new JsonContext(optionsSingleton));
}
+ [Fact]
+ public static void JsonSerializerOptions_Strict_MatchesConstructorWithJsonSerializerDefaults()
+ {
+ var options = new JsonSerializerOptions(JsonSerializerDefaults.Strict)
+ {
+ TypeInfoResolver = JsonSerializerOptions.Default.TypeInfoResolver
+ };
+
+ JsonSerializerOptions optionsSingleton = JsonSerializerOptions.Strict;
+
+ AssertExtensions.FalseExpression(options.AllowDuplicateProperties);
+ AssertExtensions.FalseExpression(options.PropertyNameCaseInsensitive);
+ AssertExtensions.TrueExpression(options.RespectNullableAnnotations);
+ AssertExtensions.TrueExpression(options.RespectRequiredConstructorParameters);
+ Assert.Equal(JsonUnmappedMemberHandling.Disallow, options.UnmappedMemberHandling);
+
+ JsonTestHelper.AssertOptionsEqual(options, optionsSingleton);
+ }
+
+ [Fact]
+ public static void JsonSerializerOptions_Strict_SerializesWithExpectedSettings()
+ {
+ JsonSerializerOptions options = JsonSerializerOptions.Strict;
+ AssertExtensions.ThrowsContains(
+ () => JsonSerializer.Deserialize>("""{"foo":1, "foo":2}""", options),
+ "Duplicate");
+ }
+
+ [Fact]
+ public static void JsonSerializerOptions_Strict_ReturnsSameInstance()
+ {
+ Assert.Same(JsonSerializerOptions.Strict, JsonSerializerOptions.Strict);
+ }
+
+ [Fact]
+ public static void JsonSerializerOptions_Strict_IsReadOnly()
+ {
+ var optionsSingleton = JsonSerializerOptions.Strict;
+ Assert.True(optionsSingleton.IsReadOnly);
+ Assert.Throws(() => optionsSingleton.AllowDuplicateProperties = true);
+ Assert.Throws(() => optionsSingleton.PropertyNameCaseInsensitive = true);
+ Assert.Throws(() => optionsSingleton.RespectNullableAnnotations = false);
+ Assert.Throws(() => optionsSingleton.RespectRequiredConstructorParameters = false);
+ Assert.Throws(() => optionsSingleton.UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip);
+
+ Assert.Throws(() => new JsonContext(optionsSingleton));
+ }
+
[Theory]
[MemberData(nameof(GetInitialTypeInfoResolversAndExpectedChains))]
public static void TypeInfoResolverChain_SetTypeInfoResolver_ReturnsExpectedChain(
@@ -1543,7 +1591,7 @@ public static void PredefinedSerializerOptions_Web()
[Theory]
[InlineData(-1)]
- [InlineData(2)]
+ [InlineData(3)]
public static void PredefinedSerializerOptions_UnhandledDefaults(int enumValue)
{
var outOfRangeSerializerDefaults = (JsonSerializerDefaults)enumValue;