.NET Core 6 Web API中如何序列化继承自Dictionary的类?
问题场景
你定义了如下继承自Dictionary<string, string>的类:
[DataContract(Name = "Item", Namespace = "")] public class Item : Dictionary<string, string> { }
控制器方法返回该类的异步枚举:
public async IAsyncEnumerable<Item> Query() { ... }
JSON请求响应符合预期,字典键值对直接作为对象根属性输出;但XML请求时抛出异常:
An error occurred while trying to create a DataContractSerializer for the type 'Item'. System.Runtime.Serialization.InvalidDataContractException: Type 'Item' cannot be ISerializable and have DataContractAttribute attribute.
需求是让任意属性直接序列化到对象根节点,而非字典作为子节点。
问题原因
Dictionary<string, string>本身实现了ISerializable接口,而DataContractSerializer不允许同一类型同时标记DataContractAttribute和实现ISerializable(默认Dictionary的序列化逻辑与数据契约不兼容),导致XML序列化失败。
解决方案
方法1:移除DataContractAttribute,自定义XML序列化逻辑
JSON序列化(无论是System.Text.Json还是Newtonsoft.Json)默认会将Dictionary的键值对解析为对象属性,无需额外配置。针对XML序列化,我们可以通过实现IXmlSerializable手动控制输出格式,同时切换ASP.NET Core的XML格式化器为XmlSerializer。
- 修改
Item类:
public class Item : Dictionary<string, string>, IXmlSerializable { public XmlSchema GetSchema() => null; public void ReadXml(XmlReader reader) { // 实现反序列化逻辑(按需添加) reader.ReadStartElement(); while (reader.NodeType != XmlNodeType.EndElement) { var key = reader.LocalName; var value = reader.ReadElementContentAsString(); Add(key, value); } reader.ReadEndElement(); } public void WriteXml(XmlWriter writer) { // 将字典键值对直接输出为根节点的子元素 foreach (var kvp in this) { writer.WriteElementString(kvp.Key, kvp.Value); } } }
- 配置ASP.NET Core使用XmlSerializer格式化器:
在Program.cs中添加XML序列化配置:
builder.Services.AddControllers() .AddXmlSerializerFormatters();
配置后,XML序列化会使用XmlSerializer而非DataContractSerializer,配合IXmlSerializable的实现,就能让字典键值对以根属性的形式输出,和JSON格式一致。
方法2:使用DynamicObject实现动态属性(更灵活)
如果需要保留DataContractAttribute或更灵活的动态属性支持,可以改用DynamicObject,同时实现IXmlSerializable控制XML输出:
[DataContract(Name = "Item", Namespace = "")] public class Item : DynamicObject, IXmlSerializable { private readonly Dictionary<string, string> _properties = new(); // 支持动态属性读取 public override bool TryGetMember(GetMemberBinder binder, out object result) { return _properties.TryGetValue(binder.Name, out result); } // 支持动态属性赋值 public override bool TrySetMember(SetMemberBinder binder, object value) { if (value is string strValue) { _properties[binder.Name] = strValue; return true; } return false; } // 供JSON序列化自动识别属性(System.Text.Json需额外配置,Newtonsoft.Json默认支持) public IEnumerable<KeyValuePair<string, string>> Properties => _properties; public XmlSchema GetSchema() => null; public void ReadXml(XmlReader reader) { reader.ReadStartElement(); while (reader.NodeType != XmlNodeType.EndElement) { var key = reader.LocalName; var value = reader.ReadElementContentAsString(); _properties.Add(key, value); } reader.ReadEndElement(); } public void WriteXml(XmlWriter writer) { foreach (var kvp in _properties) { writer.WriteElementString(kvp.Key, kvp.Value); } } }
这种方式既支持动态添加属性,又能让JSON和XML序列化都将属性输出到根节点,同时避开了Dictionary与DataContractAttribute的冲突问题。
内容的提问来源于stack exchange,提问作者Jeremy

