XML反序列化时如何忽略无效枚举值并继续执行?
我之前也遇到过类似的电子发票反序列化问题,第三方生成的XML经常会出现不符合规范的枚举值,完全卡断反序列化确实很头疼。下面给你几个可行的解决方案,你可以根据自己的场景选择:
方法1:自定义XmlReader拦截无效枚举值(推荐,兼容原生XmlSerializer)
这个方法的核心是包装原生XmlReader,在读取XML内容时提前拦截无效的枚举值,替换成你指定的默认值,同时记录错误。好处是完全兼容原生XmlSerializer,不需要修改自动生成的类。
首先定义一个错误记录类来追踪问题:
public class DeserializationError { public string ElementName { get; set; } public string InvalidValue { get; set; } public string EnumTypeName { get; set; } }
然后创建包装XmlReader的自定义类,处理枚举值的验证和替换:
// 基础XmlReader包装类,用于代理所有默认方法 public abstract class XmlReaderWrapper : XmlReader { private readonly XmlReader _innerReader; protected XmlReaderWrapper(XmlReader innerReader) { _innerReader = innerReader ?? throw new ArgumentNullException(nameof(innerReader)); } // 代理XmlReader的核心属性和方法,其余可按需补充 public override XmlNodeType NodeType => _innerReader.NodeType; public override string LocalName => _innerReader.LocalName; public override string Value => _innerReader.Value; public override bool Read() => _innerReader.Read(); public override string ReadElementContentAsString() => _innerReader.ReadElementContentAsString(); public override string GetAttribute(string name) => _innerReader.GetAttribute(name); } // 处理枚举容错的XmlReader public class TolerantEnumXmlReader : XmlReaderWrapper { private readonly List<DeserializationError> _errors; // 预存需要处理的枚举及其有效XmlEnum值映射 private readonly Dictionary<Type, Dictionary<string, object>> _enumMappings; public TolerantEnumXmlReader(XmlReader innerReader, List<DeserializationError> errors) : base(innerReader) { _errors = errors; _enumMappings = new Dictionary<Type, Dictionary<string, object>> { { typeof(PaymentMeansCodeContentType), typeof(PaymentMeansCodeContentType) .GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static) .ToDictionary( f => f.GetCustomAttribute<XmlEnumAttribute>()?.Name ?? f.Name, f => f.GetValue(null)) } // 这里可以添加其他需要容错的枚举类型 }; } public override string ReadElementContentAsString() { string elementName = LocalName; string rawValue = base.ReadElementContentAsString(); // 根据元素名匹配对应的枚举类型(假设元素名和类属性名对应) var enumMapping = _enumMappings.FirstOrDefault(m => typeof(MyType).GetProperty(elementName)?.PropertyType == m.Key); if (enumMapping.Key != null && !enumMapping.Value.ContainsKey(rawValue)) { // 记录错误信息 _errors.Add(new DeserializationError { ElementName = elementName, InvalidValue = rawValue, EnumTypeName = enumMapping.Key.Name }); // 返回默认枚举值对应的XmlEnum名称(比如ZZZ没有XmlAttribute,直接返回字段名) var defaultValue = enumMapping.Value.Values.First(v => v.Equals(PaymentMeansCodeContentType.ZZZ)); return enumMapping.Value.First(kv => kv.Value.Equals(defaultValue)).Key; } return rawValue; } // 如果枚举是XML属性,重写GetAttribute方法做类似处理 public override string GetAttribute(string name) { string rawValue = base.GetAttribute(name); // 类似元素的处理逻辑,按需添加 return rawValue; } }
最后使用这个自定义Reader进行反序列化:
var deserializationErrors = new List<DeserializationError>(); XmlSerializer serializer = new XmlSerializer(typeof(MyType)); using (var originalReader = XmlReader.Create(memoryStream)) using (var tolerantReader = new TolerantEnumXmlReader(originalReader, deserializationErrors)) { MyType invoice = serializer.Deserialize(tolerantReader) as MyType; // 处理错误记录,比如标记异常字段或者日志记录 foreach (var error in deserializationErrors) { Console.WriteLine($"警告:元素{error.ElementName}存在无效枚举值{error.InvalidValue},已替换为默认值"); // 手动确认默认值设置(可选) var property = typeof(MyType).GetProperty(error.ElementName); if (property != null) { property.SetValue(invoice, Enum.Parse(Type.GetType(error.EnumTypeName), nameof(PaymentMeansCodeContentType.ZZZ))); } } }
方法2:利用XmlSerializer的ValidationEventHandler捕获错误
这种方法不需要自定义Reader,直接通过注册验证事件来捕获枚举错误,标记错误为已处理让反序列化继续,之后再手动修正枚举值。
var errors = new List<DeserializationError>(); XmlSerializer serializer = new XmlSerializer(typeof(MyType)); // 注册验证事件处理程序 serializer.ValidationEventHandler += (sender, e) => { // 判断是否是枚举验证错误 if (e.Message.Contains("is not a valid value for")) { // 解析错误信息 var messageParts = e.Message.Split(new[] { "'", " is not a valid value for ", "." }, StringSplitOptions.RemoveEmptyEntries); if (messageParts.Length >= 3) { errors.Add(new DeserializationError { InvalidValue = messageParts[1], EnumTypeName = messageParts[2], ElementName = typeof(MyType).GetProperties() .First(p => p.PropertyType.Name == messageParts[2]).Name }); } // 标记错误已处理,让反序列化继续 e.Handled = true; } }; MyType invoice = serializer.Deserialize(memoryStream) as MyType; // 修正枚举值为你想要的默认值 foreach (var error in errors) { Type enumType = Type.GetType(error.EnumTypeName); var defaultValue = Enum.Parse(enumType, nameof(PaymentMeansCodeContentType.ZZZ)); var property = typeof(MyType).GetProperty(error.ElementName); property?.SetValue(invoice, defaultValue); }
注意:当e.Handled = true时,XmlSerializer会把枚举字段设置为枚举的默认值(通常是第一个定义的枚举值,比如你的Item10),所以需要在反序列化后手动替换成你需要的ZZZ。
方法3:使用Newtonsoft.Json的XML转换(灵活但依赖第三方库)
如果你愿意引入第三方库,Newtonsoft.Json的XML转换功能提供了更灵活的错误处理,可以直接在错误事件中设置默认值:
首先安装Newtonsoft.Json包:
Install-Package Newtonsoft.Json
然后编写代码:
var errors = new List<DeserializationError>(); var settings = new JsonSerializerSettings { Converters = new List<JsonConverter> { new StringEnumConverter { AllowIntegerValues = false } }, Error = (sender, e) => { if (e.ErrorContext.Error is JsonSerializationException jsonEx && jsonEx.Message.Contains("is not a valid value for enum")) { errors.Add(new DeserializationError { InvalidValue = e.ErrorContext.OriginalObject.ToString(), EnumTypeName = e.ErrorContext.Member.DeclaringType.Name, ElementName = e.ErrorContext.Member.Name }); // 设置默认值为ZZZ e.ErrorContext.Result = Enum.Parse(e.ErrorContext.Member.DeclaringType, nameof(PaymentMeansCodeContentType.ZZZ)); e.ErrorContext.Handled = true; } } }; // 先把XML转成JSON,再反序列化 XDocument xmlDoc = XDocument.Load(memoryStream); string json = JsonConvert.SerializeXNode(xmlDoc); MyType invoice = JsonConvert.DeserializeObject<MyType>(json, settings);
这个方法的优点是错误处理逻辑更直观,不需要处理XmlReader的细节,但需要引入第三方库。
内容的提问来源于stack exchange,提问作者Florian
相关产品推荐
相关产品推荐

