使用Newtonsoft.Json根据属性特性中指定的类型动态序列化/反序列化属性名称
Newtonsoft.Json根据属性特性中指定的类型动态序列化/反序列化属性名称
我完全懂你的困扰——用XSD生成的C#类里有个Item属性,挂了一堆XmlElementAttribute,每个都对应不同的类型和XML元素名,但Newtonsoft.Json序列化时只会固定输出"Item"这个键,你想让它根据实际赋值的类型自动换成特性里指定的名称,还得保留其他属性原有的Json.NET特性处理对吧?
你之前尝试的ContractResolver和JsonConverter思路方向都对,只是没把两者结合好。这里给你一个可行的方案,既不用重写所有序列化逻辑,又能完美实现动态属性名的需求:
第一步:提取类型与目标名称的映射关系
首先我们需要从Item属性的特性里,把每个类型对应的目标Json键名提取出来,做一个映射表:
private static Dictionary<Type, string> GetTypeToNameMap(MemberInfo member) { var typeNameMap = new Dictionary<Type, string>(); var xmlAttributes = member.GetCustomAttributes<XmlElementAttribute>(inherit: false); foreach (var attr in xmlAttributes) { if (attr.Type != null && !string.IsNullOrEmpty(attr.ElementName)) { typeNameMap[attr.Type] = attr.ElementName; } } return typeNameMap; }
第二步:自定义JsonConverter处理动态序列化/反序列化
接下来写一个专门处理Item属性的转换器,它会根据实际对象的类型查找对应的键名,同时复用Newtonsoft的默认序列化逻辑——这样你原来加在各个请求类型上的JsonProperty、JsonIgnore等特性都能正常生效,不用手动重新实现:
public class DynamicItemConverter : JsonConverter { private readonly Dictionary<Type, string> _typeToNameMap; private readonly Dictionary<string, Type> _nameToTypeMap; public DynamicItemConverter(MemberInfo member) { _typeToNameMap = GetTypeToNameMap(member); _nameToTypeMap = _typeToNameMap.ToDictionary(kv => kv.Value, kv => kv.Key); } public override bool CanConvert(Type objectType) { // 只处理object类型的属性(或者你实际的基类型) return objectType == typeof(object); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { if (value == null) { writer.WriteNull(); return; } var actualType = value.GetType(); if (_typeToNameMap.TryGetValue(actualType, out var targetName)) { // 用特性里的名称作为键,序列化对象内容 writer.WriteStartObject(); writer.WritePropertyName(targetName); serializer.Serialize(writer, value); writer.WriteEndObject(); } else { // 找不到对应类型时的降级处理,比如按默认逻辑序列化或抛出异常 serializer.Serialize(writer, value); } } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) return null; // 读取Json对象,找到唯一的属性名 var jObject = JObject.Load(reader); var targetProperty = jObject.Properties().FirstOrDefault(); if (targetProperty == null) return null; if (_nameToTypeMap.TryGetValue(targetProperty.Name, out var targetType)) { // 根据属性名找到对应类型,反序列化 return targetProperty.Value.ToObject(targetType, serializer); } else { // 找不到对应类型时的处理,比如返回null或抛出异常 return null; } } }
第三步:给Item属性绑定转换器(两种方式可选)
方式一:用ContractResolver自动绑定(推荐,无需修改生成的类)
如果你不想改动XSD生成的类,可以写一个自定义ContractResolver,自动给Item属性加上转换器:
public class DynamicItemContractResolver : DefaultContractResolver { protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { var jsonProperty = base.CreateProperty(member, memberSerialization); // 只处理名为"Item"的属性,也可以根据其他条件筛选 if (member.Name == "Item" && member.MemberType == MemberTypes.Property) { jsonProperty.Converter = new DynamicItemConverter(member); // 让转换器全权处理该属性的序列化,避免默认逻辑干扰 jsonProperty.ShouldSerialize = _ => false; } return jsonProperty; } }
序列化时使用这个ContractResolver即可:
var serializerSettings = new JsonSerializerSettings { ContractResolver = new DynamicItemContractResolver(), Formatting = Formatting.Indented // 保留你原来的其他配置 }; var jsonResult = JsonConvert.SerializeObject(yourInstance, serializerSettings);
方式二:直接给属性加特性(允许修改类时使用)
如果可以修改生成的类,也可以直接给Item属性添加转换器特性:
[JsonConverter(typeof(DynamicItemConverter), typeof(YourParentClass), "Item")] // 保留原来的XmlElementAttribute [XmlElementAttribute("ReportRequest", typeof(ReportRequestType), Form = XmlSchemaForm.Unqualified)] [XmlElementAttribute("StatusRequest", typeof(StatusRequestType), Form = XmlSchemaForm.Unqualified)] [XmlElementAttribute("TransmitRequest", typeof(TransmitRequestType), Form = XmlSchemaForm.Unqualified)] public object Item { get; set; }
这时候需要调整转换器的构造函数,支持通过参数获取属性信息:
public DynamicItemConverter(Type parentType, string propertyName) { var targetProperty = parentType.GetProperty(propertyName); _typeToNameMap = GetTypeToNameMap(targetProperty); _nameToTypeMap = _typeToNameMap.ToDictionary(kv => kv.Value, kv => kv.Key); }
方案优势
- 完全复用Newtonsoft的默认序列化逻辑,原有类型上的Json特性不受影响,不用重复造轮子
- ContractResolver方式无需修改自动生成的类,避免了重新生成代码后手动修改的麻烦
- 同时支持序列化和反序列化,完全覆盖你的需求场景
你可以根据自己的实际情况选择对应的方式,测试一下应该就能达到你想要的效果了。
备注:内容来源于stack exchange,提问作者philippe
相关产品推荐
相关产品推荐

