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 aa4daf4a839bd..5ca8dfa8d1369 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 @@ -461,7 +461,8 @@ public JsonPropertyInfo CreateJsonPropertyInfo(Type propertyType, string name) internal void CacheMember(JsonPropertyInfo jsonPropertyInfo, JsonPropertyDictionary? propertyCache, ref Dictionary? ignoredMembers) { - string memberName = jsonPropertyInfo.ClrName!; + Debug.Assert(jsonPropertyInfo.ClrName != null, "ClrName can be null in custom JsonPropertyInfo instances and should never be passed in this method"); + string memberName = jsonPropertyInfo.ClrName; // The JsonPropertyNameAttribute or naming policy resulted in a collision. if (!propertyCache!.TryAdd(jsonPropertyInfo.Name, jsonPropertyInfo)) @@ -545,7 +546,7 @@ internal void InitializeConstructorParameters(JsonParameterInfoValues[] jsonPara foreach (KeyValuePair kvp in PropertyCache.List) { JsonPropertyInfo jsonProperty = kvp.Value!; - string propertyName = jsonProperty.ClrName!; + string propertyName = jsonProperty.ClrName ?? jsonProperty.Name; ParameterLookupKey key = new(propertyName, jsonProperty.PropertyType); ParameterLookupValue value = new(jsonProperty); @@ -584,7 +585,8 @@ internal void InitializeConstructorParameters(JsonParameterInfoValues[] jsonPara else if (DataExtensionProperty != null && StringComparer.OrdinalIgnoreCase.Equals(paramToCheck.Name, DataExtensionProperty.Name)) { - ThrowHelper.ThrowInvalidOperationException_ExtensionDataCannotBindToCtorParam(DataExtensionProperty); + Debug.Assert(DataExtensionProperty.ClrName != null, "Custom property info cannot be data extension property"); + ThrowHelper.ThrowInvalidOperationException_ExtensionDataCannotBindToCtorParam(DataExtensionProperty.ClrName, DataExtensionProperty); } } 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 fdba40e285149..85cfa258d8703 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 @@ -150,7 +150,8 @@ internal void AddPropertiesUsingSourceGenInfo() { if (hasJsonInclude) { - ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(jsonPropertyInfo.ClrName!, jsonPropertyInfo.DeclaringType); + Debug.Assert(jsonPropertyInfo.ClrName != null, "ClrName is not set by source gen"); + ThrowHelper.ThrowInvalidOperationException_JsonIncludeOnNonPublicInvalid(jsonPropertyInfo.ClrName, jsonPropertyInfo.DeclaringType); } continue; 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 29c8ff7ad4161..c774aaad5bca7 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 @@ -228,9 +228,9 @@ public static void ThrowInvalidOperationException_ConstructorParameterIncomplete } [DoesNotReturn] - public static void ThrowInvalidOperationException_ExtensionDataCannotBindToCtorParam(JsonPropertyInfo jsonPropertyInfo) + public static void ThrowInvalidOperationException_ExtensionDataCannotBindToCtorParam(string propertyName, JsonPropertyInfo jsonPropertyInfo) { - throw new InvalidOperationException(SR.Format(SR.ExtensionDataCannotBindToCtorParam, jsonPropertyInfo.ClrName, jsonPropertyInfo.DeclaringType)); + throw new InvalidOperationException(SR.Format(SR.ExtensionDataCannotBindToCtorParam, propertyName, jsonPropertyInfo.DeclaringType)); } [DoesNotReturn] 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 de9082ad40b92..47259e4145f71 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 @@ -720,6 +720,70 @@ public ClassWithParametrizedConstructorAndWritableProperties(int a, string b) } } + [Fact] + public static void SerializingTypeWithCustomNonSerializablePropertyAndJsonConstructorWorksCorrectly() + { + var resolver = new DefaultJsonTypeInfoResolver { Modifiers = { ContractModifier } }; + var options = new JsonSerializerOptions { TypeInfoResolver = resolver }; + string json = JsonSerializer.Serialize(new PocoWithConstructor("str"), options); + Assert.Equal("{}", json); + + static void ContractModifier(JsonTypeInfo jti) + { + if (jti.Type == typeof(PocoWithConstructor)) + { + jti.Properties.Add(jti.CreateJsonPropertyInfo(typeof(string), "someOtherName")); + } + } + } + + [Fact] + public static void SerializingTypeWithCustomSerializablePropertyAndJsonConstructorWorksCorrectly() + { + var resolver = new DefaultJsonTypeInfoResolver { Modifiers = { ContractModifier } }; + var options = new JsonSerializerOptions { TypeInfoResolver = resolver }; + string json = JsonSerializer.Serialize(new PocoWithConstructor("str"), options); + Assert.Equal("""{"test":"asd"}""", json); + + static void ContractModifier(JsonTypeInfo jti) + { + if (jti.Type == typeof(PocoWithConstructor)) + { + JsonPropertyInfo pi = jti.CreateJsonPropertyInfo(typeof(string), "test"); + pi.Get = (o) => "asd"; + jti.Properties.Add(pi); + } + } + } + + [Fact] + public static void SerializingTypeWithCustomPropertyAndJsonConstructorBindsParameter() + { + var resolver = new DefaultJsonTypeInfoResolver { Modifiers = { ContractModifier } }; + var options = new JsonSerializerOptions { TypeInfoResolver = resolver }; + string json = """{"parameter":"asd"}"""; + PocoWithConstructor deserialized = JsonSerializer.Deserialize(json, options); + Assert.Equal("asd", deserialized.ParameterValue); + + static void ContractModifier(JsonTypeInfo jti) + { + if (jti.Type == typeof(PocoWithConstructor)) + { + jti.Properties.Add(jti.CreateJsonPropertyInfo(typeof(string), "parameter")); + } + } + } + + private class PocoWithConstructor + { + internal string ParameterValue { get; set; } + + public PocoWithConstructor(string parameter) + { + ParameterValue = parameter; + } + } + [Fact] public static void JsonConstructorAttributeIsOverridenAndPropertiesAreSetWhenCreateObjectIsSet_LargeConstructor() {