From 04a304ac1c9f8de92db39db0131330476d3c6036 Mon Sep 17 00:00:00 2001 From: HengYpinn <117615053+HengYpinn@users.noreply.github.com> Date: Wed, 12 Aug 2026 16:47:07 +0800 Subject: [PATCH] Fix renamed constructor parameter default values --- .../DefaultValueHandlingTests.cs | 49 ++++++++++++++- .../Serialization/DefaultContractResolver.cs | 63 +++++++++++-------- .../JsonSerializerInternalReader.cs | 3 +- 3 files changed, 86 insertions(+), 29 deletions(-) diff --git a/Src/Newtonsoft.Json.Tests/Serialization/DefaultValueHandlingTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/DefaultValueHandlingTests.cs index a4500ba62..daa824f6e 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/DefaultValueHandlingTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/DefaultValueHandlingTests.cs @@ -73,7 +73,7 @@ private class DefaultValueWithConstructor [DefaultValue(DefaultText)] [JsonProperty(PropertyName = "myText", DefaultValueHandling = DefaultValueHandling.Populate)] - public readonly string Text; + public string Text { get; } public DefaultValueWithConstructor([JsonProperty(PropertyName = "myText")]string text = DefaultText) { @@ -88,6 +88,51 @@ public void DefaultValueWithConstructorTest() Assert.AreEqual(DefaultValueWithConstructor.DefaultText, myObject.Text); } + private class DefaultValueWithUnmatchedConstructorParameter + { + public const string DefaultText = "..."; + + [DefaultValue(DefaultText)] + [JsonProperty(PropertyName = "myText", DefaultValueHandling = DefaultValueHandling.Populate)] + public string Text { get; } + + public DefaultValueWithUnmatchedConstructorParameter(string text = DefaultText) + { + Text = text; + } + } + + [Test] + public void DefaultValueWithUnmatchedConstructorParameterTest() + { + DefaultValueWithUnmatchedConstructorParameter myObject = JsonConvert.DeserializeObject("{}"); + Assert.IsNull(myObject.Text); + } + + private class DefaultValueWithRenamedConstructorParameter + { + public const string DefaultText = "..."; + + [DefaultValue(DefaultText)] + [JsonProperty("renamedText", DefaultValueHandling = DefaultValueHandling.Populate)] + public string Text { get; } + + public DefaultValueWithRenamedConstructorParameter( + [DefaultValue(DefaultText)] + [JsonProperty("renamedText", DefaultValueHandling = DefaultValueHandling.Populate)] + string text) + { + Text = text; + } + } + + [Test] + public void DefaultValueWithRenamedConstructorParameterTest() + { + DefaultValueWithRenamedConstructorParameter myObject = JsonConvert.DeserializeObject("{}"); + Assert.AreEqual(DefaultValueWithRenamedConstructorParameter.DefaultText, myObject.Text); + } + public class MyClass { [JsonIgnore] @@ -647,4 +692,4 @@ public enum ExportFormat Currency, Integer } -} \ No newline at end of file +} diff --git a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs index f59e8aeef..850d174b1 100644 --- a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs @@ -700,7 +700,15 @@ protected virtual IList CreateConstructorParameters(ConstructorInf continue; } - JsonProperty? matchingMemberProperty = MatchProperty(memberProperties, parameterInfo.Name, parameterInfo.ParameterType); + string propertyName = parameterInfo.Name; + JsonPropertyAttribute? propertyAttribute = JsonTypeReflector.GetAttribute(parameterInfo); + if (propertyAttribute?.PropertyName != null) + { + // Resolve an explicitly named parameter before matching it to a member property. + propertyName = ResolvePropertyNameFromAttribute(propertyAttribute.PropertyName, constructor.DeclaringType!, true, propertyAttribute); + } + + JsonProperty? matchingMemberProperty = MatchProperty(memberProperties, propertyName, parameterInfo.ParameterType); // ensure that property will have a name from matching property or from parameterinfo // parameterinfo could have no name if generated by a proxy (I'm looking at you Castle) @@ -1515,30 +1523,7 @@ private void SetPropertySettingsFromAttributes(JsonProperty property, object att hasSpecifiedName = false; } - JsonContainerAttribute? containerAttribute = JsonTypeReflector.GetAttribute(declaringType); - - NamingStrategy? namingStrategy; - if (propertyAttribute?.NamingStrategyType != null) - { - namingStrategy = JsonTypeReflector.CreateNamingStrategyInstance(propertyAttribute.NamingStrategyType, propertyAttribute.NamingStrategyParameters); - } - else if (containerAttribute?.NamingStrategyType != null) - { - namingStrategy = JsonTypeReflector.GetContainerNamingStrategy(containerAttribute); - } - else - { - namingStrategy = NamingStrategy; - } - - if (namingStrategy != null) - { - property.PropertyName = namingStrategy.GetPropertyName(mappedName, hasSpecifiedName); - } - else - { - property.PropertyName = ResolvePropertyName(mappedName); - } + property.PropertyName = ResolvePropertyNameFromAttribute(mappedName, declaringType, hasSpecifiedName, propertyAttribute); property.UnderlyingName = name; @@ -1643,6 +1628,32 @@ private void SetPropertySettingsFromAttributes(JsonProperty property, object att } } + private string ResolvePropertyNameFromAttribute(string mappedName, Type declaringType, bool hasSpecifiedName, JsonPropertyAttribute? propertyAttribute) + { + JsonContainerAttribute? containerAttribute = JsonTypeReflector.GetAttribute(declaringType); + + NamingStrategy? namingStrategy; + if (propertyAttribute?.NamingStrategyType != null) + { + namingStrategy = JsonTypeReflector.CreateNamingStrategyInstance(propertyAttribute.NamingStrategyType, propertyAttribute.NamingStrategyParameters); + } + else if (containerAttribute?.NamingStrategyType != null) + { + namingStrategy = JsonTypeReflector.GetContainerNamingStrategy(containerAttribute); + } + else + { + namingStrategy = NamingStrategy; + } + + if (namingStrategy != null) + { + return namingStrategy.GetPropertyName(mappedName, hasSpecifiedName); + } + + return ResolvePropertyName(mappedName); + } + private Predicate? CreateShouldSerializeTest(MemberInfo member) { MethodInfo? shouldSerializeMethod = member.DeclaringType!.GetMethod(JsonTypeReflector.ShouldSerializePrefix + member.Name, ReflectionUtils.EmptyTypes); @@ -1738,4 +1749,4 @@ public string GetResolvedPropertyName(string propertyName) return ResolvePropertyName(propertyName); } } -} \ No newline at end of file +} diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index d8c163d3e..2cc0fa4f6 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -2057,7 +2057,8 @@ private object CreateObjectUsingCreatorWithParameters(JsonReader reader, JsonObj JsonProperty? constructorProperty = context.ConstructorProperty; if (constructorProperty == null && context.Property != null) { - constructorProperty = contract.CreatorParameters.ForgivingCaseSensitiveFind(p => p.PropertyName!, context.Property.UnderlyingName!); + constructorProperty = contract.CreatorParameters.ForgivingCaseSensitiveFind(p => p.PropertyName!, context.Property.UnderlyingName!) + ?? contract.CreatorParameters.ForgivingCaseSensitiveFind(p => p.PropertyName!, context.Property.PropertyName!); } if (constructorProperty != null && !constructorProperty.Ignored)