You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 09:09:53