diff --git a/src/libraries/System.Text.Json/src/Resources/Strings.resx b/src/libraries/System.Text.Json/src/Resources/Strings.resx
index f091984783b60..0ebab3e5d27d6 100644
--- a/src/libraries/System.Text.Json/src/Resources/Strings.resx
+++ b/src/libraries/System.Text.Json/src/Resources/Strings.resx
@@ -696,6 +696,9 @@
JsonObjectCreationHandling.Populate is incompatible with reference handling.
+
+ JsonObjectCreationHandling.Populate is currently not supported in types with parameterized constructors.
+
Either the JSON value is not in a supported format, or is out of bounds for an Int128.
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.cs
index 304ca0a26e409..7c5d6aae1c405 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.cs
@@ -25,10 +25,10 @@ internal sealed override bool OnTryRead(ref Utf8JsonReader reader, Type typeToCo
{
JsonTypeInfo jsonTypeInfo = state.Current.JsonTypeInfo;
- if (jsonTypeInfo.CreateObject != null || state.Current.IsPopulating)
+ if (!jsonTypeInfo.UsesParameterizedConstructor || state.Current.IsPopulating)
{
// Fall back to default object converter in following cases:
- // - if user has set a default constructor delegate with contract customization
+ // - if user configuration has invalidated the parameterized constructor
// - we're continuing populating an object.
return base.OnTryRead(ref reader, typeToConvert, options, ref state, out value);
}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs
index 8ee9f3db0283b..c654a920f8ff6 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/DefaultJsonTypeInfoResolver.Helpers.cs
@@ -38,12 +38,20 @@ internal static MemberAccessor MemberAccessor
private static JsonTypeInfo CreateTypeInfoCore(Type type, JsonConverter converter, JsonSerializerOptions options)
{
JsonTypeInfo typeInfo = JsonTypeInfo.CreateJsonTypeInfo(type, converter, options);
- typeInfo.NumberHandling = GetNumberHandlingForType(typeInfo.Type);
- typeInfo.PreferredPropertyObjectCreationHandling = GetObjectCreationHandlingForType(typeInfo.Type);
- if (typeInfo.Kind == JsonTypeInfoKind.Object)
+ if (GetNumberHandlingForType(typeInfo.Type) is { } numberHandling)
{
- typeInfo.UnmappedMemberHandling = GetUnmappedMemberHandling(typeInfo.Type);
+ typeInfo.NumberHandling = numberHandling;
+ }
+
+ if (GetObjectCreationHandlingForType(typeInfo.Type) is { } creationHandling)
+ {
+ typeInfo.PreferredPropertyObjectCreationHandling = creationHandling;
+ }
+
+ if (GetUnmappedMemberHandling(typeInfo.Type) is { } unmappedMemberHandling)
+ {
+ typeInfo.UnmappedMemberHandling = unmappedMemberHandling;
}
typeInfo.PopulatePolymorphismMetadata();
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 0ab5c08d7825b..e2234093474e0 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
@@ -495,9 +495,17 @@ private void DetermineEffectiveObjectCreationHandlingForProperty()
Debug.Assert(ParentTypeInfo != null, "We should have ensured parent is assigned in JsonTypeInfo");
Debug.Assert(!IsConfigured, "Should not be called post-configuration.");
+ JsonObjectCreationHandling effectiveObjectCreationHandling = JsonObjectCreationHandling.Replace;
if (ObjectCreationHandling == null)
{
- JsonObjectCreationHandling preferredCreationHandling = ParentTypeInfo.PreferredPropertyObjectCreationHandling ?? Options.PreferredObjectCreationHandling;
+ // Consult type-level configuration, then global configuration.
+ // Ignore global configuration if we're using a parameterized constructor.
+ JsonObjectCreationHandling preferredCreationHandling =
+ ParentTypeInfo.PreferredPropertyObjectCreationHandling
+ ?? (ParentTypeInfo.DetermineUsesParameterizedConstructor()
+ ? JsonObjectCreationHandling.Replace
+ : Options.PreferredObjectCreationHandling);
+
bool canPopulate =
preferredCreationHandling == JsonObjectCreationHandling.Populate &&
EffectiveConverter.CanPopulate &&
@@ -506,7 +514,7 @@ private void DetermineEffectiveObjectCreationHandlingForProperty()
!ParentTypeInfo.SupportsPolymorphicDeserialization &&
!(Set == null && IgnoreReadOnlyMember);
- EffectiveObjectCreationHandling = canPopulate ? JsonObjectCreationHandling.Populate : JsonObjectCreationHandling.Replace;
+ effectiveObjectCreationHandling = canPopulate ? JsonObjectCreationHandling.Populate : JsonObjectCreationHandling.Replace;
}
else if (ObjectCreationHandling == JsonObjectCreationHandling.Populate)
{
@@ -537,18 +545,24 @@ private void DetermineEffectiveObjectCreationHandlingForProperty()
ThrowHelper.ThrowInvalidOperationException_ObjectCreationHandlingPropertyCannotAllowReadOnlyMember(this);
}
- EffectiveObjectCreationHandling = JsonObjectCreationHandling.Populate;
- }
- else
- {
- Debug.Assert(EffectiveObjectCreationHandling == JsonObjectCreationHandling.Replace);
+ effectiveObjectCreationHandling = JsonObjectCreationHandling.Populate;
}
- if (EffectiveObjectCreationHandling == JsonObjectCreationHandling.Populate &&
- Options.ReferenceHandlingStrategy != ReferenceHandlingStrategy.None)
+ if (effectiveObjectCreationHandling is JsonObjectCreationHandling.Populate)
{
- ThrowHelper.ThrowInvalidOperationException_ObjectCreationHandlingPropertyCannotAllowReferenceHandling();
+ if (ParentTypeInfo.DetermineUsesParameterizedConstructor())
+ {
+ ThrowHelper.ThrowNotSupportedException_ObjectCreationHandlingPropertyDoesNotSupportParameterizedConstructors();
+ }
+
+ if (Options.ReferenceHandlingStrategy != ReferenceHandlingStrategy.None)
+ {
+ ThrowHelper.ThrowInvalidOperationException_ObjectCreationHandlingPropertyCannotAllowReferenceHandling();
+ }
}
+
+ // Validation complete, commit configuration.
+ EffectiveObjectCreationHandling = effectiveObjectCreationHandling;
}
private bool NumberHandingIsApplicable()
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.Cache.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.Cache.cs
index 86a7af256a78a..5a901fbb80eae 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.Cache.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.Cache.cs
@@ -35,6 +35,14 @@ public abstract partial class JsonTypeInfo
// All of the serializable parameters on a POCO constructor keyed on parameter name.
// Only parameters which bind to properties are cached.
internal JsonPropertyDictionary? ParameterCache { get; private set; }
+ internal bool UsesParameterizedConstructor
+ {
+ get
+ {
+ Debug.Assert(IsConfigured);
+ return ParameterCache != null;
+ }
+ }
// All of the serializable properties on a POCO (except the optional extension property) keyed on property name.
internal JsonPropertyDictionary? PropertyCache { get; private set; }
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 b9e9fe60d2b23..a1a184f6059d2 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
@@ -552,17 +552,14 @@ public JsonObjectCreationHandling? PreferredPropertyObjectCreationHandling
{
VerifyMutable();
- if (value is not null)
+ if (Kind != JsonTypeInfoKind.Object)
{
- if (Kind != JsonTypeInfoKind.Object)
- {
- ThrowHelper.ThrowInvalidOperationException_JsonTypeInfoOperationNotPossibleForKind(Kind);
- }
+ ThrowHelper.ThrowInvalidOperationException_JsonTypeInfoOperationNotPossibleForKind(Kind);
+ }
- if (!JsonSerializer.IsValidCreationHandlingValue(value.Value))
- {
- throw new ArgumentOutOfRangeException(nameof(value));
- }
+ if (value is not null && !JsonSerializer.IsValidCreationHandlingValue(value.Value))
+ {
+ throw new ArgumentOutOfRangeException(nameof(value));
}
_preferredPropertyObjectCreationHandling = value;
@@ -684,7 +681,7 @@ private void Configure()
{
ConfigureProperties();
- if (Converter.ConstructorIsParameterized)
+ if (DetermineUsesParameterizedConstructor())
{
ConfigureConstructorParameters();
}
@@ -808,6 +805,12 @@ bool IsCurrentNodeCompatible()
///
private bool IsCompatibleWithCurrentOptions { get; set; } = true;
+ ///
+ /// Determine if the current configuration is compatible with using a parameterized constructor.
+ ///
+ internal bool DetermineUsesParameterizedConstructor()
+ => Converter.ConstructorIsParameterized && CreateObject is null;
+
#if DEBUG
internal string GetPropertyDebugInfo(ReadOnlySpan unescapedPropertyName)
{
@@ -1107,7 +1110,7 @@ internal void ConfigureProperties()
internal void ConfigureConstructorParameters()
{
Debug.Assert(Kind == JsonTypeInfoKind.Object);
- Debug.Assert(Converter.ConstructorIsParameterized);
+ Debug.Assert(DetermineUsesParameterizedConstructor());
Debug.Assert(PropertyCache is not null);
Debug.Assert(ParameterCache is null);
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs
index 59a47bc3ac7bc..25c067cc10930 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/ReadStack.cs
@@ -386,20 +386,20 @@ public JsonTypeInfo GetTopJsonTypeInfoWithParameterizedConstructor()
for (int i = 0; i < _count - 1; i++)
{
- if (_stack[i].JsonTypeInfo.Converter.ConstructorIsParameterized)
+ if (_stack[i].JsonTypeInfo.UsesParameterizedConstructor)
{
return _stack[i].JsonTypeInfo;
}
}
- Debug.Assert(Current.JsonTypeInfo.Converter.ConstructorIsParameterized);
+ Debug.Assert(Current.JsonTypeInfo.UsesParameterizedConstructor);
return Current.JsonTypeInfo;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetConstructorArgumentState()
{
- if (Current.JsonTypeInfo.Converter.ConstructorIsParameterized)
+ if (Current.JsonTypeInfo.UsesParameterizedConstructor)
{
Current.CtorArgumentState ??= new();
}
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 7072d9e302008..5b05ff243a80a 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
@@ -99,6 +99,12 @@ public static void ThrowInvalidOperationException_ObjectCreationHandlingProperty
throw new InvalidOperationException(SR.ObjectCreationHandlingPropertyCannotAllowReferenceHandling);
}
+ [DoesNotReturn]
+ public static void ThrowNotSupportedException_ObjectCreationHandlingPropertyDoesNotSupportParameterizedConstructors()
+ {
+ throw new NotSupportedException(SR.ObjectCreationHandlingPropertyDoesNotSupportParameterizedConstructors);
+ }
+
[DoesNotReturn]
public static void ThrowJsonException_SerializationConverterRead(JsonConverter? converter)
{
diff --git a/src/libraries/System.Text.Json/tests/Common/JsonCreationHandlingTests.Object.cs b/src/libraries/System.Text.Json/tests/Common/JsonCreationHandlingTests.Object.cs
index aae9da6b2c628..e25adffef53fa 100644
--- a/src/libraries/System.Text.Json/tests/Common/JsonCreationHandlingTests.Object.cs
+++ b/src/libraries/System.Text.Json/tests/Common/JsonCreationHandlingTests.Object.cs
@@ -1165,4 +1165,52 @@ public class ClassWithInvalidPropertyAnnotation
[JsonObjectCreationHandling((JsonObjectCreationHandling)(-1))]
public List Property { get; }
}
+
+ [Theory]
+ [InlineData(typeof(ClassWithParameterizedConstructorWithPopulateProperty))]
+ [InlineData(typeof(ClassWithParameterizedConstructorWithPopulateType))]
+ public async Task ClassWithParameterizedCtor_UsingPopulateConfiguration_ThrowsNotSupportedException(Type type)
+ {
+ object instance = Activator.CreateInstance(type, "Jim");
+ string json = """{"Username":"Jim","PhoneNumbers":["123456"]}""";
+
+ await Assert.ThrowsAsync(() => Serializer.SerializeWrapper(instance, type));
+ await Assert.ThrowsAsync(() => Serializer.DeserializeWrapper(json, type));
+ Assert.Throws(() => Serializer.GetTypeInfo(type));
+ }
+
+ public class ClassWithParameterizedConstructorWithPopulateProperty(string name)
+ {
+ public string Name { get; } = name;
+
+ [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
+ public List PhoneNumbers { get; } = new();
+ }
+
+ [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
+ public class ClassWithParameterizedConstructorWithPopulateType(string name)
+ {
+ public string Name { get; } = name;
+
+ public List PhoneNumbers { get; } = new();
+ }
+
+ [Fact]
+ public async Task ClassWithParameterizedCtor_NoPopulateConfiguration_WorksWithGlobalPopulateConfiguration()
+ {
+ string json = """{"Username":"Jim","PhoneNumbers":["123456"]}""";
+
+ JsonSerializerOptions options = Serializer.CreateOptions(makeReadOnly: false);
+ options.PreferredObjectCreationHandling = JsonObjectCreationHandling.Populate;
+
+ ClassWithParameterizedConstructorNoPopulate result = await Serializer.DeserializeWrapper(json, options);
+ Assert.Empty(result.PhoneNumbers);
+ }
+
+ public class ClassWithParameterizedConstructorNoPopulate(string name)
+ {
+ public string Name { get; } = name;
+
+ public List PhoneNumbers { get; } = new();
+ }
}
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonCreationHandlingTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonCreationHandlingTests.cs
index eab6f939b9367..5862387200f6a 100644
--- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonCreationHandlingTests.cs
+++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/JsonCreationHandlingTests.cs
@@ -278,6 +278,9 @@ public sealed class JsonCreationHandlingTests_AsyncStreamWithSmallBuffer()
[JsonSerializable(typeof(SimpleClassWitNonPopulatableProperty))]
[JsonSerializable(typeof(ClassWithInvalidTypeAnnotation))]
[JsonSerializable(typeof(ClassWithInvalidPropertyAnnotation))]
+ [JsonSerializable(typeof(ClassWithParameterizedConstructorWithPopulateProperty))]
+ [JsonSerializable(typeof(ClassWithParameterizedConstructorWithPopulateType))]
+ [JsonSerializable(typeof(ClassWithParameterizedConstructorNoPopulate))]
internal partial class CreationHandlingTestContext : JsonSerializerContext
{
}
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/DefaultJsonTypeInfoResolverTests.JsonTypeInfo.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/DefaultJsonTypeInfoResolverTests.JsonTypeInfo.cs
index 06fd59bae037e..bc6e3a28ff78f 100644
--- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/DefaultJsonTypeInfoResolverTests.JsonTypeInfo.cs
+++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/MetadataTests/DefaultJsonTypeInfoResolverTests.JsonTypeInfo.cs
@@ -1440,11 +1440,10 @@ public static void PreferredPropertyObjectCreationHandling_NonObjectKind_ThrowsI
{
JsonTypeInfo jsonTypeInfo = JsonTypeInfo.CreateJsonTypeInfo(type, new());
- // Invalid kinds default to null and can be set to null.
- Assert.Null(jsonTypeInfo.PreferredPropertyObjectCreationHandling);
- jsonTypeInfo.PreferredPropertyObjectCreationHandling = null;
+ // Invalid kinds default to null.
Assert.Null(jsonTypeInfo.PreferredPropertyObjectCreationHandling);
+ Assert.Throws(() => jsonTypeInfo.PreferredPropertyObjectCreationHandling = null);
Assert.Throws(() => jsonTypeInfo.PreferredPropertyObjectCreationHandling = JsonObjectCreationHandling.Populate);
Assert.Throws(() => jsonTypeInfo.PreferredPropertyObjectCreationHandling = JsonObjectCreationHandling.Replace);
Assert.Null(jsonTypeInfo.PreferredPropertyObjectCreationHandling);