.NET Core无样板代码序列化嵌套接口属性方案咨询
首先明确核心限制:.NET 内置的XmlSerializer原生不支持直接序列化接口类型属性,你在接口属性上添加[XmlElement(Type = typeof(XXX))]不生效的原因是:序列化器做类型校验时,只要检测到属性声明类型为接口,会直接抛出异常,根本不会读取属性上标注的类型映射,在接口定义上加任何特性都无法绕过这个校验逻辑。
以下方案完全基于.NET Core内置的System.Text.Json和XmlSerializer实现,不需要引入任何第三方框架,不需要修改原有接口定义、不需要把接口属性改成具体类、不需要改动private set访问修饰符,也不会生成带多余类型标记的序列化结果。
System.Text.Json 实现方案
这个方案几乎没有冗余代码,只需要实现一个通用的接口类型转换器,统一配置接口和具体实现类的映射关系即可,生成的JSON无任何额外冗余字段。
- 先写通用的接口转换泛型类,所有接口映射都可以复用:
using System.Text.Json; using System.Text.Json.Serialization; public class InterfaceConverter<TInterface, TImpl> : JsonConverter<TInterface> where TImpl : class, TInterface { public override TInterface? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => JsonSerializer.Deserialize<TImpl>(ref reader, options); public override void Write(Utf8JsonWriter writer, TInterface value, JsonSerializerOptions options) => JsonSerializer.Serialize(writer, (TImpl)value!, options); } - 序列化/反序列化时统一配置映射关系即可:
var jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, Converters = { // 按实际的接口和实现类配对添加即可 new InterfaceConverter<IEngine, Engine>(), new InterfaceConverter<IModul, Modul>() } }; // 序列化 string json = JsonSerializer.Serialize(carObj, jsonOptions); // 反序列化,原生支持private set属性赋值 Car? deserializedCar = JsonSerializer.Deserialize<Car>(json, jsonOptions);
如果同一个接口存在多个实现类,可以扩展这个转换器,在序列化时写入简单的类型标识、反序列化时匹配对应实现类即可,也不会产生多余的样板代码。
XmlSerializer 实现方案
XmlSerializer的校验逻辑更严格,无法完全零配置实现接口序列化,但可以通过XmlAttributeOverrides全局配置类型映射,不需要在业务实体上加任何特性、不需要写重复的代理属性,生成的XML完全干净,没有xsi:type这类多余的类型标记。
注意:
XmlSerializer动态生成的序列化程序集必须做静态缓存,不要每次序列化都新建实例,否则会造成内存泄漏。
配置代码如下:
// 初始化映射配置,全局只执行一次 var xmlOverrides = new XmlAttributeOverrides(); // 1. 配置Car类的Engine属性映射到Engine具体实现 var carEngineAttr = new XmlAttributes(); carEngineAttr.XmlElements.Add(new XmlElementAttribute(typeof(Engine))); xmlOverrides.Add(typeof(Car), nameof(Car.Engine), carEngineAttr); // 2. 配置IEngine的MainModul属性映射到Modul具体实现 var engineMainModulAttr = new XmlAttributes(); engineMainModulAttr.XmlElements.Add(new XmlElementAttribute(typeof(Modul))); xmlOverrides.Add(typeof(Engine), nameof(IEngine.MainModul), engineMainModulAttr); // 3. 配置IEngine的AdditionalModules列表内的IModul项映射到Modul具体实现 var moduleListAttr = new XmlAttributes(); moduleListAttr.XmlArrayItems.Add(new XmlArrayItemAttribute(typeof(Modul))); xmlOverrides.Add(typeof(Engine), nameof(IEngine.AdditionalModules), moduleListAttr); // 生成序列化器,存入静态缓存全局复用 var xmlSerializer = new XmlSerializer(typeof(Car), xmlOverrides);
使用这个序列化器实例做序列化/反序列化时,完全可以识别接口类型属性,支持private set属性赋值,输出的XML和直接用具体类作为属性类型的序列化结果完全一致。如果同一个接口对应多个实现类,只需要在对应XmlElements/XmlArrayItems集合中把所有可能的实现类都添加进去即可,序列化时会自动根据实际实例类型匹配XML元素名,反序列化也能正常识别。
关于特性标注的疑问
不存在仅靠在接口属性上加内置特性就能直接完成接口序列化的方案,这是两个内置序列化器源码层面写死的逻辑:接口属于抽象类型,序列化器无法在没有配置的情况下自动判定要使用的具体实现类,必须显式指定映射关系。
内容的提问来源于stack exchange,提问作者Behold The Light

