diff --git a/src/libraries/System.Text.Json/Common/JsonUnmappedMemberHandling.cs b/src/libraries/System.Text.Json/Common/JsonUnmappedMemberHandling.cs
new file mode 100644
index 00000000000000..dccd7a33fb03ec
--- /dev/null
+++ b/src/libraries/System.Text.Json/Common/JsonUnmappedMemberHandling.cs
@@ -0,0 +1,27 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Text.Json.Serialization
+{
+ ///
+ /// Determines how handles JSON properties that
+ /// cannot be mapped to a specific .NET member when deserializing object types.
+ ///
+#if BUILDING_SOURCE_GENERATOR
+ internal
+#else
+ public
+#endif
+ enum JsonUnmappedMemberHandling
+ {
+ ///
+ /// Silently skips any unmapped properties. This is the default behavior.
+ ///
+ Skip = 0,
+
+ ///
+ /// Throws an exception when an unmapped property is encountered.
+ ///
+ Disallow = 1,
+ }
+}
diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
index 55c7e5ad90df36..9743eaec41f3e3 100644
--- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
+++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
@@ -31,6 +31,7 @@ private sealed partial class Emitter
private const string PropertyInfoVarName = "propertyInfo";
internal const string JsonContextVarName = "jsonContext";
private const string NumberHandlingPropName = "NumberHandling";
+ private const string UnmappedMemberHandlingPropName = "UnmappedMemberHandling";
private const string ObjectCreatorPropName = "ObjectCreator";
private const string OptionsInstanceVariableName = "Options";
private const string JsonTypeInfoReturnValueLocalVariableName = "jsonTypeInfo";
@@ -64,6 +65,7 @@ private sealed partial class Emitter
private const string JsonCollectionInfoValuesTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonCollectionInfoValues";
private const string JsonIgnoreConditionTypeRef = "global::System.Text.Json.Serialization.JsonIgnoreCondition";
private const string JsonNumberHandlingTypeRef = "global::System.Text.Json.Serialization.JsonNumberHandling";
+ private const string JsonUnmappedMemberHandlingTypeRef = "global::System.Text.Json.Serialization.JsonUnmappedMemberHandling";
private const string JsonMetadataServicesTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonMetadataServices";
private const string JsonObjectInfoValuesTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonObjectInfoValues";
private const string JsonParameterInfoValuesTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonParameterInfoValues";
@@ -646,6 +648,14 @@ private string GenerateForObject(TypeGenerationSpec typeMetadata)
{JsonTypeInfoReturnValueLocalVariableName} = {JsonMetadataServicesTypeRef}.CreateObjectInfo<{typeMetadata.TypeRef}>({OptionsLocalVariableName}, {ObjectInfoVarName});";
+ if (typeMetadata.UnmappedMemberHandling != null)
+ {
+ objectInfoInitSource += $"""
+
+ {JsonTypeInfoReturnValueLocalVariableName}.{UnmappedMemberHandlingPropName} = {GetUnmappedMemberHandlingAsStr(typeMetadata.UnmappedMemberHandling.Value)};
+""";
+ }
+
string additionalSource = @$"{propMetadataInitFuncSource}{serializeFuncSource}{ctorParamMetadataInitFuncSource}";
return GenerateForType(typeMetadata, objectInfoInitSource, additionalSource);
@@ -1392,6 +1402,9 @@ private static string GetNumberHandlingAsStr(JsonNumberHandling? numberHandling)
? $"({JsonNumberHandlingTypeRef}){(int)numberHandling.Value}"
: "default";
+ private static string GetUnmappedMemberHandlingAsStr(JsonUnmappedMemberHandling unmappedMemberHandling) =>
+ $"({JsonUnmappedMemberHandlingTypeRef}){(int)unmappedMemberHandling}";
+
private static string GetCreateValueInfoMethodRef(string typeCompilableName) => $"{CreateValueInfoMethodName}<{typeCompilableName}>";
private static string FormatBool(bool value) => value ? "true" : "false";
diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs
index f06442f07ed433..6b3ca7fe542964 100644
--- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs
+++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs
@@ -38,6 +38,7 @@ private sealed class Parser
private const string JsonIgnoreConditionFullName = "System.Text.Json.Serialization.JsonIgnoreCondition";
private const string JsonIncludeAttributeFullName = "System.Text.Json.Serialization.JsonIncludeAttribute";
private const string JsonNumberHandlingAttributeFullName = "System.Text.Json.Serialization.JsonNumberHandlingAttribute";
+ private const string JsonUnmappedMemberHandlingAttributeFullName = "System.Text.Json.Serialization.JsonUnmappedMemberHandlingAttribute";
private const string JsonPropertyNameAttributeFullName = "System.Text.Json.Serialization.JsonPropertyNameAttribute";
private const string JsonPropertyOrderAttributeFullName = "System.Text.Json.Serialization.JsonPropertyOrderAttribute";
private const string JsonRequiredAttributeFullName = "System.Text.Json.Serialization.JsonRequiredAttribute";
@@ -706,6 +707,7 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener
List? propertyInitializerSpecList = null;
CollectionType collectionType = CollectionType.NotApplicable;
JsonNumberHandling? numberHandling = null;
+ JsonUnmappedMemberHandling? unmappedMemberHandling = null;
bool foundDesignTimeCustomConverter = false;
string? converterInstatiationLogic = null;
bool implementsIJsonOnSerialized = false;
@@ -727,6 +729,12 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener
numberHandling = (JsonNumberHandling)ctorArgs[0].Value!;
continue;
}
+ else if (attributeTypeFullName == JsonUnmappedMemberHandlingAttributeFullName)
+ {
+ IList ctorArgs = attributeData.ConstructorArguments;
+ unmappedMemberHandling = (JsonUnmappedMemberHandling)ctorArgs[0].Value!;
+ continue;
+ }
else if (!foundDesignTimeCustomConverter && attributeType.GetCompatibleBaseClass(JsonConverterAttributeFullName) != null)
{
foundDesignTimeCustomConverter = true;
@@ -1130,6 +1138,7 @@ void CacheMemberHelper(Location memberLocation)
generationMode,
classType,
numberHandling,
+ unmappedMemberHandling,
propGenSpecList,
paramGenSpecArray,
propertyInitializerSpecList,
diff --git a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets
index 81ab875fdd87ab..6c977db11d6bbe 100644
--- a/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets
+++ b/src/libraries/System.Text.Json/gen/System.Text.Json.SourceGeneration.targets
@@ -42,6 +42,7 @@
+
diff --git a/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs b/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs
index e8dfb07ac6bd12..b65b2862516476 100644
--- a/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs
+++ b/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs
@@ -62,6 +62,7 @@ public TypeGenerationSpec(Type type)
public bool CanBeNull { get; private set; }
public JsonNumberHandling? NumberHandling { get; private set; }
+ public JsonUnmappedMemberHandling? UnmappedMemberHandling { get; private set; }
public List? PropertyGenSpecList { get; private set; }
@@ -129,6 +130,7 @@ public void Initialize(
JsonSourceGenerationMode generationMode,
ClassType classType,
JsonNumberHandling? numberHandling,
+ JsonUnmappedMemberHandling? unmappedMemberHandling,
List? propertyGenSpecList,
ParameterGenerationSpec[]? ctorParamGenSpecArray,
List? propertyInitializerSpecList,
@@ -153,6 +155,7 @@ public void Initialize(
CanBeNull = !IsValueType || nullableUnderlyingTypeMetadata != null;
IsPolymorphic = isPolymorphic;
NumberHandling = numberHandling;
+ UnmappedMemberHandling = unmappedMemberHandling;
PropertyGenSpecList = propertyGenSpecList;
PropertyInitializerSpecList = propertyInitializerSpecList;
CtorParamGenSpecArray = ctorParamGenSpecArray;
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 24c19d8c55c108..81dd1d06194d10 100644
--- a/src/libraries/System.Text.Json/ref/System.Text.Json.cs
+++ b/src/libraries/System.Text.Json/ref/System.Text.Json.cs
@@ -385,6 +385,7 @@ public JsonSerializerOptions(System.Text.Json.JsonSerializerOptions options) { }
public System.Text.Json.Serialization.ReferenceHandler? ReferenceHandler { get { throw null; } set { } }
public System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver? TypeInfoResolver { 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 WriteIndented { get { throw null; } set { } }
public void AddContext() where TContext : System.Text.Json.Serialization.JsonSerializerContext, new() { }
[System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Getting a converter for a type may require reflection which depends on runtime code generation.")]
@@ -1036,6 +1037,17 @@ public enum JsonUnknownTypeHandling
JsonElement = 0,
JsonNode = 1,
}
+ public enum JsonUnmappedMemberHandling
+ {
+ Skip = 0,
+ Disallow = 1,
+ }
+ [System.AttributeUsageAttribute(System.AttributeTargets.Class | System.AttributeTargets.Interface | System.AttributeTargets.Struct, AllowMultiple=false, Inherited=false)]
+ public partial class JsonUnmappedMemberHandlingAttribute : System.Text.Json.Serialization.JsonAttribute
+ {
+ public JsonUnmappedMemberHandlingAttribute(System.Text.Json.Serialization.JsonUnmappedMemberHandling unmappedMemberHandling) { }
+ public System.Text.Json.Serialization.JsonUnmappedMemberHandling UnmappedMemberHandling { get { throw null; } }
+ }
public abstract partial class ReferenceHandler
{
protected ReferenceHandler() { }
@@ -1235,6 +1247,7 @@ internal JsonTypeInfo() { }
public System.Text.Json.Serialization.Metadata.JsonPolymorphismOptions? PolymorphismOptions { get { throw null; } set { } }
public System.Collections.Generic.IList Properties { get { throw null; } }
public System.Type Type { get { throw null; } }
+ public System.Text.Json.Serialization.JsonUnmappedMemberHandling? UnmappedMemberHandling { get { throw null; } set { } }
[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 and might need runtime code generation. Use System.Text.Json source generation for native AOT applications.")]
public System.Text.Json.Serialization.Metadata.JsonPropertyInfo CreateJsonPropertyInfo(System.Type propertyType, string name) { throw null; }
diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx
index f719671a08174f..57076fff30ad99 100644
--- a/src/libraries/System.Text.Json/src/Resources/Strings.resx
+++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx
@@ -360,6 +360,9 @@
The type '{0}' cannot have more than one member that has the attribute '{1}'.
+
+ The type '{0}' is marked 'JsonUnmappedMemberHandling.Disallow' which conflicts with extension data property '{1}'.
+
The type '{0}' is not supported.
@@ -479,6 +482,9 @@
The metadata property is either not supported by the type or is not the first property in the deserialized JSON object.
+
+ The JSON property '{0}' could not be mapped to any .NET member contained in type '{1}'.
+
Deserialized object contains a duplicate type discriminator metadata property.
diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj
index 32758d44325f7b..f43694170d27f1 100644
--- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj
+++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj
@@ -33,6 +33,7 @@ The System.Text.Json library is built-in as part of the shared framework in .NET
+
@@ -104,6 +105,7 @@ The System.Text.Json library is built-in as part of the shared framework in .NET
+
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonUnmappedMemberHandlingAttribute.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonUnmappedMemberHandlingAttribute.cs
new file mode 100644
index 00000000000000..6004ea91d82ed3
--- /dev/null
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonUnmappedMemberHandlingAttribute.cs
@@ -0,0 +1,27 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Text.Json.Serialization
+{
+ ///
+ /// When placed on a type, determines the configuration
+ /// for the specific type, overriding the global setting.
+ ///
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct,
+ AllowMultiple = false, Inherited = false)]
+ public class JsonUnmappedMemberHandlingAttribute : JsonAttribute
+ {
+ ///
+ /// Initializes a new instance of .
+ ///
+ public JsonUnmappedMemberHandlingAttribute(JsonUnmappedMemberHandling unmappedMemberHandling)
+ {
+ UnmappedMemberHandling = unmappedMemberHandling;
+ }
+
+ ///
+ /// Specifies the unmapped member handling setting for the attribute.
+ ///
+ public JsonUnmappedMemberHandling UnmappedMemberHandling { get; }
+ }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs
index 5f3d59a569f0ef..972a33db7d3470 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonConverterOfT.cs
@@ -638,7 +638,7 @@ internal sealed override void WriteAsPropertyNameCoreAsObject(Utf8JsonWriter wri
// For consistency do not return any default converters for options instances linked to a
// JsonSerializerContext, even if the default converters might have been rooted.
- if (!IsInternalConverter && options.SerializerContext is null)
+ if (!IsInternalConverter && options.TypeInfoResolver is not JsonSerializerContext)
{
result = _fallbackConverterForPropertyNameSerialization;
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Helpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Helpers.cs
index 5713cf079581df..b0141c05428d20 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Helpers.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Helpers.cs
@@ -78,5 +78,8 @@ internal static bool IsValidNumberHandlingValue(JsonNumberHandling handling) =>
JsonNumberHandling.AllowReadingFromString |
JsonNumberHandling.WriteAsString |
JsonNumberHandling.AllowNamedFloatingPointLiterals));
+
+ internal static bool IsValidUnmappedMemberHandlingValue(JsonUnmappedMemberHandling handling) =>
+ handling is JsonUnmappedMemberHandling.Skip or JsonUnmappedMemberHandling.Disallow;
}
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs
index c3d6916546ab4d..707997aff8d31f 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandlePropertyName.cs
@@ -24,17 +24,18 @@ internal static JsonPropertyInfo LookupProperty(
out bool useExtensionProperty,
bool createExtensionProperty = true)
{
+ JsonTypeInfo jsonTypeInfo = state.Current.JsonTypeInfo;
#if DEBUG
- if (state.Current.JsonTypeInfo.Kind != JsonTypeInfoKind.Object)
+ if (jsonTypeInfo.Kind != JsonTypeInfoKind.Object)
{
string objTypeName = obj?.GetType().FullName ?? "";
- Debug.Fail($"obj.GetType() => {objTypeName}; {state.Current.JsonTypeInfo.GetPropertyDebugInfo(unescapedPropertyName)}");
+ Debug.Fail($"obj.GetType() => {objTypeName}; {jsonTypeInfo.GetPropertyDebugInfo(unescapedPropertyName)}");
}
#endif
useExtensionProperty = false;
- JsonPropertyInfo jsonPropertyInfo = state.Current.JsonTypeInfo.GetProperty(
+ JsonPropertyInfo jsonPropertyInfo = jsonTypeInfo.GetProperty(
unescapedPropertyName,
ref state.Current,
out byte[] utf8PropertyName);
@@ -45,11 +46,18 @@ internal static JsonPropertyInfo LookupProperty(
// For case insensitive and missing property support of JsonPath, remember the value on the temporary stack.
state.Current.JsonPropertyName = utf8PropertyName;
- // Determine if we should use the extension property.
+ // Handle missing properties
if (jsonPropertyInfo == JsonPropertyInfo.s_missingProperty)
{
- JsonPropertyInfo? dataExtProperty = state.Current.JsonTypeInfo.ExtensionDataProperty;
- if (dataExtProperty != null && dataExtProperty.HasGetter && dataExtProperty.HasSetter)
+ if (jsonTypeInfo.EffectiveUnmappedMemberHandling is JsonUnmappedMemberHandling.Disallow)
+ {
+ Debug.Assert(jsonTypeInfo.ExtensionDataProperty is null, "jsonTypeInfo.Configure() should have caught conflicting configuration.");
+ string stringPropertyName = JsonHelpers.Utf8GetString(unescapedPropertyName);
+ ThrowHelper.ThrowJsonException_UnmappedJsonProperty(jsonTypeInfo.Type, stringPropertyName);
+ }
+
+ // Determine if we should use the extension property.
+ if (jsonTypeInfo.ExtensionDataProperty is JsonPropertyInfo { HasGetter: true, HasSetter: true } dataExtProperty)
{
state.Current.JsonPropertyNameAsString = JsonHelpers.Utf8GetString(unescapedPropertyName);
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 f4e02f566e5b42..ceca2369a07f02 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
@@ -291,6 +291,7 @@ public bool Equals(JsonSerializerOptions? left, JsonSerializerOptions? right)
left._defaultIgnoreCondition == right._defaultIgnoreCondition &&
left._numberHandling == right._numberHandling &&
left._unknownTypeHandling == right._unknownTypeHandling &&
+ left._unmappedMemberHandling == right._unmappedMemberHandling &&
left._defaultBufferSize == right._defaultBufferSize &&
left._maxDepth == right._maxDepth &&
left._allowTrailingCommas == right._allowTrailingCommas &&
@@ -336,6 +337,7 @@ public int GetHashCode(JsonSerializerOptions options)
AddHashCode(ref hc, options._defaultIgnoreCondition);
AddHashCode(ref hc, options._numberHandling);
AddHashCode(ref hc, options._unknownTypeHandling);
+ AddHashCode(ref hc, options._unmappedMemberHandling);
AddHashCode(ref hc, options._defaultBufferSize);
AddHashCode(ref hc, options._maxDepth);
AddHashCode(ref hc, options._allowTrailingCommas);
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 f58bfa024be3c8..cfc36739317afe 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
@@ -62,6 +62,7 @@ public static JsonSerializerOptions Default
private JsonIgnoreCondition _defaultIgnoreCondition;
private JsonNumberHandling _numberHandling;
private JsonUnknownTypeHandling _unknownTypeHandling;
+ private JsonUnmappedMemberHandling _unmappedMemberHandling;
private int _defaultBufferSize = BufferSizeDefault;
private int _maxDepth;
@@ -107,6 +108,7 @@ public JsonSerializerOptions(JsonSerializerOptions options)
_defaultIgnoreCondition = options._defaultIgnoreCondition;
_numberHandling = options._numberHandling;
_unknownTypeHandling = options._unknownTypeHandling;
+ _unmappedMemberHandling = options._unmappedMemberHandling;
_defaultBufferSize = options._defaultBufferSize;
_maxDepth = options._maxDepth;
@@ -550,6 +552,20 @@ public JsonUnknownTypeHandling UnknownTypeHandling
}
}
+ ///
+ /// Determines how handles JSON properties that
+ /// cannot be mapped to a specific .NET member when deserializing object types.
+ ///
+ public JsonUnmappedMemberHandling UnmappedMemberHandling
+ {
+ get => _unmappedMemberHandling;
+ set
+ {
+ VerifyMutable();
+ _unmappedMemberHandling = value;
+ }
+ }
+
///
/// Defines whether JSON should pretty print which includes:
/// indenting nested JSON tokens, adding new lines, and adding white space between property names and values.
@@ -585,14 +601,12 @@ public ReferenceHandler? ReferenceHandler
}
}
- internal JsonSerializerContext? SerializerContext => _typeInfoResolver as JsonSerializerContext;
-
internal bool CanUseFastPathSerializationLogic
{
get
{
Debug.Assert(IsReadOnly);
- return _canUseFastPathSerializationLogic ??= SerializerContext?.CanUseFastPathSerializationLogic(this) ?? false;
+ return _canUseFastPathSerializationLogic ??= _typeInfoResolver is JsonSerializerContext ctx ? ctx.CanUseFastPathSerializationLogic(this) : false;
}
}
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 c752e30292c6f5..a73e1c23cf99b9 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
@@ -289,11 +289,6 @@ internal void EnsureConfigured()
internal void Configure()
{
- Debug.Assert(ParentTypeInfo != null, "We should have ensured parent is assigned in JsonTypeInfo");
- Debug.Assert(!ParentTypeInfo.IsConfigured);
-
- DeclaringTypeNumberHandling = ParentTypeInfo.NumberHandling;
-
if (!IsForTypeInfo)
{
CacheNameAsUtf8BytesAndEscapedNameSection();
@@ -443,7 +438,12 @@ private void DetermineSerializationCapabilities()
private void DetermineNumberHandlingForTypeInfo()
{
- if (DeclaringTypeNumberHandling != null && DeclaringTypeNumberHandling != JsonNumberHandling.Strict && !EffectiveConverter.IsInternalConverter)
+ Debug.Assert(ParentTypeInfo != null, "We should have ensured parent is assigned in JsonTypeInfo");
+ Debug.Assert(!ParentTypeInfo.IsConfigured);
+
+ JsonNumberHandling? declaringTypeNumberHandling = ParentTypeInfo.NumberHandling;
+
+ if (declaringTypeNumberHandling != null && declaringTypeNumberHandling != JsonNumberHandling.Strict && !EffectiveConverter.IsInternalConverter)
{
ThrowHelper.ThrowInvalidOperationException_NumberHandlingOnPropertyInvalid(this);
}
@@ -454,7 +454,7 @@ private void DetermineNumberHandlingForTypeInfo()
// custom collections e.g. public class MyNumberList : List.
// Priority 1: Get handling from the type (parent type in this case is the type itself).
- EffectiveNumberHandling = DeclaringTypeNumberHandling;
+ EffectiveNumberHandling = declaringTypeNumberHandling;
// Priority 2: Get handling from JsonSerializerOptions instance.
if (!EffectiveNumberHandling.HasValue && Options.NumberHandling != JsonNumberHandling.Strict)
@@ -466,6 +466,7 @@ private void DetermineNumberHandlingForTypeInfo()
private void DetermineNumberHandlingForProperty()
{
+ Debug.Assert(ParentTypeInfo != null, "We should have ensured parent is assigned in JsonTypeInfo");
Debug.Assert(!IsConfigured, "Should not be called post-configuration.");
Debug.Assert(_jsonTypeInfo != null, "Must have already been determined on configuration.");
@@ -474,7 +475,7 @@ private void DetermineNumberHandlingForProperty()
if (numberHandlingIsApplicable)
{
// Priority 1: Get handling from attribute on property/field, its parent class type or property type.
- JsonNumberHandling? handling = NumberHandling ?? DeclaringTypeNumberHandling ?? _jsonTypeInfo.NumberHandling;
+ JsonNumberHandling? handling = NumberHandling ?? ParentTypeInfo.NumberHandling ?? _jsonTypeInfo.NumberHandling;
// Priority 2: Get handling from JsonSerializerOptions instance.
if (!handling.HasValue && Options.NumberHandling != JsonNumberHandling.Strict)
@@ -825,11 +826,6 @@ internal JsonTypeInfo JsonTypeInfo
///
internal bool SrcGen_IsPublic { get; set; }
- ///
- /// Number handling for declaring type
- ///
- internal JsonNumberHandling? DeclaringTypeNumberHandling { get; set; }
-
///
/// Gets or sets the applied to the current property.
///
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs
index dc020c39b3e9d7..034864ca8160ae 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs
@@ -446,9 +446,12 @@ internal JsonTypeInfo? KeyTypeInfo
///
/// The instance has been locked for further modification.
///
+ ///
+ /// Specified an invalid value.
+ ///
///
/// For contracts originating from or ,
- /// the value of this callback will be mapped from any annotations.
+ /// the value of this callback will be mapped from any annotations.
///
public JsonNumberHandling? NumberHandling
{
@@ -456,12 +459,60 @@ public JsonNumberHandling? NumberHandling
set
{
VerifyMutable();
+
+ if (value is not null && !JsonSerializer.IsValidNumberHandlingValue(value.Value))
+ {
+ throw new ArgumentOutOfRangeException(nameof(value));
+ }
+
_numberHandling = value;
}
}
private JsonNumberHandling? _numberHandling;
+ ///
+ /// Gets or sets the type-level override.
+ ///
+ ///
+ /// The instance has been locked for further modification.
+ ///
+ /// -or-
+ ///
+ /// Unmapped member handling only supported for .
+ ///
+ ///
+ /// Specified an invalid value.
+ ///
+ ///
+ /// For contracts originating from or ,
+ /// the value of this callback will be mapped from any annotations.
+ ///
+ public JsonUnmappedMemberHandling? UnmappedMemberHandling
+ {
+ get => _unmappedMemberHandling;
+ set
+ {
+ VerifyMutable();
+
+ if (Kind != JsonTypeInfoKind.Object)
+ {
+ ThrowHelper.ThrowInvalidOperationException_JsonTypeInfoOperationNotPossibleForKind(Kind);
+ }
+
+ if (value is not null && !JsonSerializer.IsValidUnmappedMemberHandlingValue(value.Value))
+ {
+ throw new ArgumentOutOfRangeException(nameof(value));
+ }
+
+ _unmappedMemberHandling = value;
+ }
+ }
+
+ private JsonUnmappedMemberHandling? _unmappedMemberHandling;
+
+ internal JsonUnmappedMemberHandling EffectiveUnmappedMemberHandling { get; private set; }
+
internal JsonTypeInfo(Type type, JsonConverter converter, JsonSerializerOptions options)
{
Type = type;
@@ -565,7 +616,7 @@ internal void Configure()
if (converter.ConstructorIsParameterized)
{
- InitializeConstructorParameters(GetParameterInfoValues(), sourceGenMode: Options.SerializerContext != null);
+ InitializeConstructorParameters(GetParameterInfoValues(), sourceGenMode: Options.TypeInfoResolver is JsonSerializerContext);
}
}
@@ -785,6 +836,11 @@ internal void CacheMember(JsonPropertyInfo jsonPropertyInfo, JsonPropertyDiction
if (jsonPropertyInfo.IsExtensionData)
{
+ if (UnmappedMemberHandling is JsonUnmappedMemberHandling.Disallow)
+ {
+ ThrowHelper.ThrowInvalidOperationException_ExtensionDataConflictsWithUnmappedMemberHandling(Type, jsonPropertyInfo);
+ }
+
if (ExtensionDataProperty != null)
{
ThrowHelper.ThrowInvalidOperationException_SerializationDuplicateTypeAttribute(Type, typeof(JsonExtensionDataAttribute));
@@ -907,6 +963,11 @@ internal void InitializePropertyCache()
{
if (property.IsExtensionData)
{
+ if (UnmappedMemberHandling is JsonUnmappedMemberHandling.Disallow)
+ {
+ ThrowHelper.ThrowInvalidOperationException_ExtensionDataConflictsWithUnmappedMemberHandling(Type, property);
+ }
+
if (ExtensionDataProperty != null)
{
ThrowHelper.ThrowInvalidOperationException_SerializationDuplicateTypeAttribute(Type, typeof(JsonExtensionDataAttribute));
@@ -957,6 +1018,12 @@ internal void InitializePropertyCache()
}
NumberOfRequiredProperties = numberOfRequiredProperties;
+ // Override global UnmappedMemberHandling configuration
+ // if type specifies an extension data property.
+ EffectiveUnmappedMemberHandling = UnmappedMemberHandling ??
+ (ExtensionDataProperty is null
+ ? Options.UnmappedMemberHandling
+ : JsonUnmappedMemberHandling.Skip);
}
internal void InitializeConstructorParameters(JsonParameterInfoValues[] jsonParameters, bool sourceGenMode = false)
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionJsonTypeInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionJsonTypeInfoOfT.cs
index fee942254b8015..57143057dbec7a 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionJsonTypeInfoOfT.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/ReflectionJsonTypeInfoOfT.cs
@@ -20,6 +20,11 @@ internal ReflectionJsonTypeInfo(JsonConverter converter, JsonSerializerOptions o
: base(converter, options)
{
NumberHandling = GetNumberHandlingForType(Type);
+ if (Kind == JsonTypeInfoKind.Object)
+ {
+ UnmappedMemberHandling = GetUnmappedMemberHandling(Type);
+ }
+
PopulatePolymorphismMetadata();
MapInterfaceTypesToCallbacks();
@@ -232,6 +237,12 @@ private void CacheMember(
return numberHandlingAttribute?.Handling;
}
+ private static JsonUnmappedMemberHandling? GetUnmappedMemberHandling(Type type)
+ {
+ JsonUnmappedMemberHandlingAttribute? numberHandlingAttribute = type.GetUniqueCustomAttribute(inherit: false);
+ return numberHandlingAttribute?.UnmappedMemberHandling;
+ }
+
private static bool PropertyIsOverriddenAndIgnored(
string currentMemberName,
Type currentMemberType,
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/SourceGenJsonTypeInfoOfT.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/SourceGenJsonTypeInfoOfT.cs
index e16b1592bf6f5b..c3e28a729e0271 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/SourceGenJsonTypeInfoOfT.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/SourceGenJsonTypeInfoOfT.cs
@@ -129,7 +129,7 @@ internal override void LateAddProperties()
return;
}
- JsonSerializerContext? context = Options.SerializerContext;
+ JsonSerializerContext? context = Options.TypeInfoResolver as JsonSerializerContext;
JsonPropertyInfo[] array;
if (PropInitFunc == null || (array = PropInitFunc(context!)) == null)
{
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 b1e3627eccbb66..6fe76016a365dc 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
@@ -463,6 +463,12 @@ public static void ThrowInvalidOperationException_SerializationDuplicateTypeAttr
throw new InvalidOperationException(SR.Format(SR.SerializationDuplicateTypeAttribute, classType, typeof(TAttribute)));
}
+ [DoesNotReturn]
+ public static void ThrowInvalidOperationException_ExtensionDataConflictsWithUnmappedMemberHandling(Type classType, JsonPropertyInfo jsonPropertyInfo)
+ {
+ throw new InvalidOperationException(SR.Format(SR.ExtensionDataConflictsWithUnmappedMemberHandling, classType, jsonPropertyInfo.MemberName));
+ }
+
[DoesNotReturn]
public static void ThrowInvalidOperationException_SerializationDataExtensionPropertyInvalid(JsonPropertyInfo jsonPropertyInfo)
{
@@ -590,6 +596,12 @@ public static void ThrowJsonException_MetadataUnexpectedProperty(ReadOnlySpan
public abstract partial class JsonSerializerWrapper
{
+ ///
+ /// Either JsonSerializerOptions.Default for reflection or the JsonSerializerContext.Options for source gen.
+ ///
public abstract JsonSerializerOptions DefaultOptions { get; }
///
@@ -39,6 +42,17 @@ public abstract partial class JsonSerializerWrapper
public abstract Task