使用XmlSerializer将空xml属性值反序列化为nullable int属性

Ali*_*kau 73 .net xml serialization nullable

我从第三方获得了一个xml,我需要将它反序列化为C#对象.此xml可能包含值为整数类型或空值的属性:attr ="11"或attr ="".我想将此属性值反序列化为类型为可空整数的属性.但XmlSerializer不支持反序列化为可空类型.在使用InvalidOperationException创建XmlSerializer期间,以下测试代码失败{"有一个错误反映了类型'TestConsoleApplication.SerializeMe'."}.

[XmlRoot("root")]
public class SerializeMe
{
    [XmlElement("element")]
    public Element Element { get; set; }
}

public class Element
{
    [XmlAttribute("attr")]
    public int? Value { get; set; }
}

class Program {
    static void Main(string[] args) {
        string xml = "<root><element attr=''>valE</element></root>";
        var deserializer = new XmlSerializer(typeof(SerializeMe));
        Stream xmlStream = new MemoryStream(Encoding.ASCII.GetBytes(xml));
        var result = (SerializeMe)deserializer.Deserialize(xmlStream);
    }
}
Run Code Online (Sandbox Code Playgroud)

当我将'Value'属性的类型更改为int时,反序列化失败并出现InvalidOperationException:

XML文档中存在错误(1,16).

任何人都可以建议如何将具有空值的属性反序列化为可空类型(作为null),同时将非空属性值反序列化为整数?有没有任何技巧,所以我不必手动对每个字段进行反序列化(实际上有很多)?

来自ahsteele的评论后更新:

  1. Xsi:nil属性

    据我所知,此属性仅适用于XmlElementAttribute - 此属性指定元素没有内容,无论是子元素还是正文.但我需要找到XmlAttributeAttribute的解决方案.无论如何我不能改变xml,因为我无法控制它.

  2. bool*指定的属性

    仅当属性值为非空或缺少属性时,此属性才有效.当attr具有空值(attr ='')时,XmlSerializer构造函数失败(如预期的那样).

    public class Element
    {
        [XmlAttribute("attr")]
        public int Value { get; set; }
    
        [XmlIgnore]
        public bool ValueSpecified;
    }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 像Alex Scordellis这样的博客文章中的自定义Nullable类

    我试图从这篇博文中采用这个课程来解决我的问题:

    [XmlAttribute("attr")]
    public NullableInt Value { get; set; } 
    
    Run Code Online (Sandbox Code Playgroud)

    但是XmlSerializer构造函数因InvalidOperationException而失败:

    无法序列化TestConsoleApplication.NullableInt类型的成员'Value'.

    XmlAttribute/XmlText不能用于编码实现IXmlSerializable的类型}

  4. 丑陋的代理解决方案(我在这里写这段代码的耻辱:)):

    public class Element
    {
        [XmlAttribute("attr")]
        public string SetValue { get; set; }
    
        public int? GetValue()
        {
            if ( string.IsNullOrEmpty(SetValue) || SetValue.Trim().Length <= 0 )
                return null;
    
            int result;
            if (int.TryParse(SetValue, out result))
                return result;
    
            return null;
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    但我不想提出这样的解决方案,因为它打破了我的类的接口为其消费者.我最好手动实现IXmlSerializable接口.

目前看起来我必须为整个Element类实现IXmlSerializable(它很大)并且没有简单的解决方法......

小智 57

这应该工作:

[XmlIgnore]
public int? Age { get; set; }

[XmlElement("Age")]
public string AgeAsText
{
  get { return (Age.HasValue) ? Age.ToString() : null; } 
  set { Age = !string.IsNullOrEmpty(value) ? int.Parse(value) : default(int?); }
}
Run Code Online (Sandbox Code Playgroud)

  • FWIW,我发现这个解决方案比明确的IXmlSerializable实现(接受的解决方案)更好,尽管不是OP的具体问题.除非绝对需要,否则我会避免实施IXmlSerializable,发现它在长期维护中会花费更多的成本.在这样一个简单的情况下,没有任何其他缓解因素,我会选择"丑陋"的代理解决方案,而不再考虑它. (9认同)
  • 这将有效,但这与我的问题中的数字4)相同.我不想将代理字段引入我班级的公共界面.谢谢 (4认同)

Ali*_*kau 20

我通过实现IXmlSerializable接口解决了这个问题.我没有找到更简单的方法.

这是测试代码示例:

[XmlRoot("root")]
public class DeserializeMe {
    [XmlArray("elements"), XmlArrayItem("element")]
    public List<Element> Element { get; set; }
}

public class Element : IXmlSerializable {
    public int? Value1 { get; private set; }
    public float? Value2 { get; private set; }

    public void ReadXml(XmlReader reader) {
        string attr1 = reader.GetAttribute("attr");
        string attr2 = reader.GetAttribute("attr2");
        reader.Read();

        Value1 = ConvertToNullable<int>(attr1);
        Value2 = ConvertToNullable<float>(attr2);
    }

    private static T? ConvertToNullable<T>(string inputValue) where T : struct {
        if ( string.IsNullOrEmpty(inputValue) || inputValue.Trim().Length == 0 ) {
            return null;
        }

        try {
            TypeConverter conv = TypeDescriptor.GetConverter(typeof(T));
            return (T)conv.ConvertFrom(inputValue);
        }
        catch ( NotSupportedException ) {
            // The conversion cannot be performed
            return null;
        }
    }

    public XmlSchema GetSchema() { return null; }
    public void WriteXml(XmlWriter writer) { throw new NotImplementedException(); }
}

class TestProgram {
    public static void Main(string[] args) {
        string xml = @"<root><elements><element attr='11' attr2='11.3'/><element attr='' attr2=''/></elements></root>";
        XmlSerializer deserializer = new XmlSerializer(typeof(DeserializeMe));
        Stream xmlStream = new MemoryStream(Encoding.ASCII.GetBytes(xml));
        var result = (DeserializeMe)deserializer.Deserialize(xmlStream);
    }
}
Run Code Online (Sandbox Code Playgroud)


ahs*_*ele 11

我最近一直在搞乱序列化很多,并且在处理值类型的空数据时发现以下文章和帖子很有帮助.

如何在C#中使用XmlSerializer使值类型可以为空可以得到答案- 序列化详细介绍了XmlSerializer的一个非常好的技巧.具体来说,XmlSerialier查找XXXSpecified布尔属性以确定是否应该包含它以允许您忽略空值.

Alex Scordellis问StackOverflow问题得到了一个很好的答案.Alex也在他的博客上写了一篇关于他试图解决的问题的好文章.使用XmlSerializer反序列化为Nullable <int>.

有关Xsi:nil属性绑定支持的MSDN文档也很有用.与IXmlSerializable接口上的文档一样,尽管编写自己的实现应该是最后的选择.