使用XmlSerializer将空的xml属性值反序列化为可为null的int属性


74

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

[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);
    }
}

当我将“值”属性的类型更改为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;
    }
    
  3. 自定义Nullable类,如Alex Scordellis在此博客文章中所述

    我试图通过本博客文章中的课程来解决我的问题:

    [XmlAttribute("attr")]
    public NullableInt Value { get; set; } 
    

    但是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;
        }
    }
    

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

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

Answers:


61

这应该工作:

[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?); }
}

4
这将起作用,但这与我的问题中的4)相同。我不想将替代字段引入班级的公共接口中。谢谢
Aliaksei Kliuchnikau 09年

9
FWIW,我发现此解决方案比显式IXmlSerializable实现(公认的解决方案)要好,尽管不是针对OP的特定问题。除非绝对需要,否则我避免实施IXmlSerializable,因为发现IXmlSerializable会导致长期的维护费用增加。在诸如此类的简单情况下,没有任何其他缓解因素,我将继续考虑“丑陋”的替代解决方案,而无需三思。
Paul Prewett 2014年

会带来一些额外的开销,但是您当然可以有两个类-一个用于反序列化,它具有所有这些额外的属性,而另一个仅具有实际值。创建一个隐式转换,该转换仅返回具有所有正确信息的要转换为的类的新实例。
TheHitchenator '20

21

我通过实现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);
    }
}

12

我最近很忙于序列化,并且发现以下文章和帖子在处理值类型的空数据时很有用。

如何在C#中使用XmlSerializer使值类型可为空的答案-序列化详细说明了XmlSerializer的一个漂亮技巧。具体来说,XmlSerialier将查找XXXSpecified布尔属性,以确定是否应包含该属性,该属性使您可以忽略空值。

Alex Scordellis问了一个StackOverflow问题,该问题得到了很好的回答。Alex还在他的博客上做了一篇很好的文章,内容涉及他试图解决的问题,该问题试图使用XmlSerializer反序列化为Nullable <int>

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


1
“使用XmlSerializer反序列化为Nullable”链接无效。这是Google的缓存版本
Anttu 2014年

@Anttu我将答案中的链接切换到原始使用XmlSerializer的Wayback Machine存档的答案中,以反序列化为Nullable <int>
ahsteele 2014年

2

我想我也应该把答案丢给别人:通过创建实现IXmlSerializable接口的自定义类型来解决此问题:

假设您有一个具有以下节点的XML对象:

<ItemOne>10</Item2>
<ItemTwo />

表示它们的对象:

public class MyItems {
    [XmlElement("ItemOne")]
    public int ItemOne { get; set; }

    [XmlElement("ItemTwo")]
    public CustomNullable<int> ItemTwo { get; set; } // will throw exception if empty element and type is int
}

动态可为空的结构以表示任何潜在的可为空的条目以及转换

public struct CustomNullable<T> : IXmlSerializable where T: struct {
    private T value;
    private bool hasValue;

    public bool HasValue {
        get { return hasValue; }
    }

    public T Value {
        get { return value; }
    }

    private CustomNullable(T value) {
        this.hasValue = true;
        this.value = value;
    }

    public XmlSchema GetSchema() {
        return null;
    }

    public void ReadXml(XmlReader reader) {
        string strValue = reader.ReadString();
        if (String.IsNullOrEmpty(strValue)) {
            this.hasValue = false;
        }
        else {
            T convertedValue = strValue.To<T>();
            this.value = convertedValue;
            this.hasValue = true;
        }
        reader.ReadEndElement();

    }

    public void WriteXml(XmlWriter writer) {
        throw new NotImplementedException();
    }

    public static implicit operator CustomNullable<T>(T value) {
        return new CustomNullable<T>(value);
    }

}

public static class ObjectExtensions {
    public static T To<T>(this object value) {
        Type t = typeof(T);
        // Get the type that was made nullable.
        Type valueType = Nullable.GetUnderlyingType(typeof(T));
        if (valueType != null) {
            // Nullable type.
            if (value == null) {
                // you may want to do something different here.
                return default(T);
            }
            else {
                // Convert to the value type.
                object result = Convert.ChangeType(value, valueType);
                // Cast the value type to the nullable type.
                return (T)result;
            }
        }
        else {
            // Not nullable.
            return (T)Convert.ChangeType(value, typeof(T));
        }
    }
}

1

您还可以通过将加载xml到中XmlDocument,然后将其反序列化为Json所需的对象T来执行此操作。

        public static T XmlToModel<T>(string xml)
        {

            XmlDocument doc = new XmlDocument();
            doc.LoadXml(xml);

            string jsonText = JsonConvert.SerializeXmlNode(doc);

            T result = JsonConvert.DeserializeObject<T>(jsonText);

            return result;
        }


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.