如何让JsonConverterAttribute与JsonConvert.Deserialize使用相同命名策略
问题:JsonConverterAttribute与蛇形命名策略兼容问题
场景复现
现有如下待序列化的类:
class Container { [JsonConverter(typeof(PropertyConverter))] public PropertyBase SomeProperty { get; set; } }
使用以下代码进行序列化:
var settings = new JsonSerializerSettings { ContractResolver = new DefaultContractResolver { NamingStrategy = new SnakeCaseNamingStrategy() } }; var serialized = JsonConvert.SerializeObject( new Container { SomeProperty = new PropertyImplementationA { PropertyA = "some string value" } }, settings);
但执行反序列化时:
var deserialized = JsonConvert.DeserializeObject<Container>(serialized, settings);
转换器并未生效。排查后发现,移除SnakeCaseNamingStrategy后一切正常,说明标记JsonConverterAttribute的属性,其注册的转换器未使用全局配置的命名策略。
约束条件
- 临时方案:在序列化器设置中全局注册转换器,但创建设置时无法预知所有需要的转换器,不可行
- 显式标记
[JsonProperty("property_base")]:需要在代码库中大量修改标记,不可行 - 不能取消
SnakeCaseNamingStrategy:这是代码库既定规则,无法修改
核心问题
如何让标记JsonConverterAttribute的属性,与JsonConvert.DeserializeObject传入的JsonSerializerSettings使用相同的命名策略?
解决方案:自定义契约解析器,传递全局命名策略给转换器
问题根源在于,当属性标记JsonConverterAttribute时,Newtonsoft会为该属性创建独立的序列化上下文,未继承全局的命名策略。我们可以通过自定义契约解析器,将全局命名策略传递给转换器。
步骤1:自定义契约解析器
public class SnakeCaseContractResolverWithConverterSupport : DefaultContractResolver { public SnakeCaseContractResolverWithConverterSupport() { NamingStrategy = new SnakeCaseNamingStrategy(); } protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { var property = base.CreateProperty(member, memberSerialization); // 检测属性是否带有JsonConverterAttribute var converterAttr = member.GetCustomAttribute<JsonConverterAttribute>(); if (converterAttr != null && property.Converter != null) { // 创建带有全局命名策略的序列化设置 var converterSettings = new JsonSerializerSettings { ContractResolver = this, NamingStrategy = this.NamingStrategy }; // 将设置传递给自定义转换器(需转换器支持接收设置) if (property.Converter is PropertyConverter customConverter) { customConverter.SerializerSettings = converterSettings; } } return property; } }
步骤2:修改自定义转换器,支持使用传入的设置
调整PropertyConverter,让它使用传递过来的全局设置进行序列化/反序列化:
public class PropertyConverter : JsonConverter<PropertyBase> { // 接收全局传递的序列化设置 public JsonSerializerSettings SerializerSettings { get; set; } public override void WriteJson(JsonWriter writer, PropertyBase value, JsonSerializer serializer) { // 使用全局设置序列化,保证命名策略一致 JsonConvert.SerializeObject(value, writer, SerializerSettings); } public override PropertyBase ReadJson(JsonReader reader, Type objectType, PropertyBase existingValue, bool hasExistingValue, JsonSerializer serializer) { var jsonObject = JObject.Load(reader); // 根据实际类型标识判断具体实现类,示例中以property_a字段为例 if (jsonObject.ContainsKey("property_a")) { // 使用全局设置反序列化 return jsonObject.ToObject<PropertyImplementationA>(JsonSerializer.Create(SerializerSettings)); } // 其他类型判断逻辑... return existingValue ?? Activator.CreateInstance(objectType) as PropertyBase; } }
步骤3:使用自定义契约解析器
替换原设置中的契约解析器:
var settings = new JsonSerializerSettings { ContractResolver = new SnakeCaseContractResolverWithConverterSupport() }; // 序列化和反序列化代码保持不变 var serialized = JsonConvert.SerializeObject(new Container { SomeProperty = new PropertyImplementationA { PropertyA = "some string value" } }, settings); var deserialized = JsonConvert.DeserializeObject<Container>(serialized, settings);
原理说明
自定义契约解析器在生成属性契约时,会自动检测带有JsonConverterAttribute的属性,并将全局的命名策略通过序列化设置传递给自定义转换器。这样转换器在处理属性时,就会使用和全局一致的蛇形命名策略,解决了两者不兼容的问题。
内容的提问来源于stack exchange,提问作者Jay
相关产品推荐
相关产品推荐

