Newtonsoft JsonConvert.SerializeObject会忽略JsonProperty, 如果名称是大写的。

4
我希望能够使用CamelCasePropertyNameContractResolver,但需要针对特定属性名称进行覆盖。为此,我使用JsonProperty属性。这个方法很好用,但当我选择的名称全部大写时,它就无法正常工作了。有什么建议或解决方法吗?
在下面的示例中,当我不使用CamelCasePropertyNameContractResolver时,Bar被序列化为“BAR”,但当我使用解析器时,它被序列化为“bar”。Foo和CamelCaseProperty在两种情况下都被正确地序列化。
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace ConsoleTester
{
    class Program
    {
        static void Main(string[] args)
        {
            var foo = new FooBar {CamelCaseProperty = "test", Foo = "test", Bar = "test" };
            var output = JsonConvert.SerializeObject(foo);
            // output "CamelCaseProperty", "fOO", "BAR"

            var output2 = JsonConvert.SerializeObject(foo, new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() });
            // output "camelCaseProperty", "fOO", "bar"
        }
    }

    public class FooBar
    {
        public string CamelCaseProperty { get; set; }
        [JsonProperty("fOO")]
        public string Foo { get; set; }
        [JsonProperty("BAR")]
        public string Bar { get; set; }
    }
}

1
不要使用 new CamelCasePropertyNamesContractResolver(),而是使用 new DefaultContractResolver { NamingStrategy = new CamelCaseNamingStrategy() }。关于为什么,请参见这里 - dbc
2个回答

15
你看到这个的原因是 CamelCasePropertyNamesContractResolver 有意设计为覆盖 字典键显式设置属性名 的大小写,可以从参考源代码中看到:
public CamelCasePropertyNamesContractResolver()
{
    NamingStrategy = new CamelCaseNamingStrategy
    {
        ProcessDictionaryKeys = true,
        OverrideSpecifiedNames = true
    };
}

如果您不想这样做,有几个选项可以防止显式名称的大小写而不创建自己的自定义合同解析器类型。
首先,您可以使用 DefaultContractResolverNamingStrategy = new CamelCaseNamingStrategy() 进行序列化:
var settings = new JsonSerializerSettings 
{ 
    ContractResolver = new DefaultContractResolver { NamingStrategy = new CamelCaseNamingStrategy() }
};
var output2 = JsonConvert.SerializeObject(foo, settings);

这意味着CamelCaseNamingStrategy.OverrideSpecifiedNames保持默认值为false

其次,如果您无法访问框架的合同解析器,可以在特定属性上设置JsonPropertyAttribute.NamingStrategyType = typeof(DefaultNamingStrategy),如下所示:

public class FooBar
{
    public string CamelCaseProperty { get; set; }

    [JsonProperty("fOO")]
    public string Foo { get; set; }

    [JsonProperty("BAR", NamingStrategyType = typeof(DefaultNamingStrategy))]
    public string Bar { get; set; }
}

第三点,如果你希望你的整个对象忽略当前合同解析器的命名策略,你可以将[JsonObject(NamingStrategyType = typeof(TNamingStrategy))]应用于你的对象:

[JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
public class FooBar
{
    public string CamelCaseProperty { get; set; }

    [JsonProperty("fOO")]
    public string Foo { get; set; }

    [JsonProperty("BAR")]
    public string Bar { get; set; }
}

注:

  • 虽然可以修改CamelCasePropertyNamesContractResolver实例的命名策略,但由于后者全局共享每种类型的所有实例的合同信息,这可能会导致意外的副作用,如果您的应用程序尝试使用多个CamelCasePropertyNamesContractResolver实例。没有DefaultContractResolver的这样的问题,因此在需要任何大小写逻辑的自定义时使用它更安全。

  • 使用或子类化DefaultContractResolver时,您可能希望缓存契约解析器以获得最佳性能,因为它不会跨每种类型的所有实例共享合同信息。

  • 我不知道Json.NET的驼峰解析器为什么设计为覆盖指定的名称,可能是出于历史原因。

  • 命名策略首先在Json.NET 9.0.1中引入,因此此答案仅适用于该版本及更高版本。


在我的情况下,第二种变体效果最好。我需要将整个对象序列化为驼峰式,并仅将少数属性序列化为PascalCase。 - aleha_84

0

当您使用ContractResolver时,JsonProperty属性不会被识别。

要解决此问题,您可以覆盖ContractResolver:

public class MyResolver : CamelCasePropertyNamesContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var property = base.CreateProperty(member, memberSerialization);
        if(member.GetCustomAttribute<JsonPropertyAttribute>() is JsonPropertyAttribute jsonProperty)
        {
            property.PropertyName = jsonProperty.PropertyName;
        }

        return property;
    }
}

并使用您的解析器:

var output2 = JsonConvert.SerializeObject(foo, new JsonSerializerSettings { ContractResolver = new MyResolver() });

在某种程度上,必须尊重JsonProperty属性。否则,为什么属性Foo会被序列化为fOO而不是foo呢? - André B
是的,你说得对。但它在使用ContractResolver之前会优先考虑JsonProperty。所以fOO是驼峰式命名,而BAR是小写字母开头的驼峰式命名。 - KelvynRisso

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接