使用空元素反序列化Xml

9

考虑以下XML:

<a>
    <b>2</b>
    <c></c>
</a>  

我需要将此XML反序列化为一个对象。因此,我编写了以下类。
public class A
{
    [XmlElement("b", Namespace = "")]
    public int? B { get; set; }

    [XmlElement("c", Namespace = "")]
    public int? C { get; set; }

}

由于我使用了可为空类型,因此当对上述XML进行反序列化时,我期望会得到一个具有空C属性的A对象。

但是事实并非如此,我得到了一个异常,提示文档存在错误。


<c></c> 不是 null。它是一个零长度的字符串。 - Wesley Long
3个回答

11

一个 缺失的 元素和一个 null 元素是有区别的。

对于缺失的元素,例如 <a><b>2</b></a>,在这里 C 会采用你指定的默认值(使用 DefaultValue 属性),如果没有明确的默认值,则为 null。

对于 null 元素,例如 <a><b>2</b><c xs:Nil='true'/></a>,你将会得到 null。

当你做 <a><b>2</b><c></c><a/> 时,xml 序列化程序将尝试将 string.Empty 解析为整数并正确地失败。

由于你的提供程序生成了无效的 xml,如果使用 XmlSerializer,则需要这样做:

[XmlRoot(ElementName = "a")]
public class A
{
    [XmlElement(ElementName = "b")]
    public int? B { get; set; }

    [XmlElement(ElementName = "c")]
    public string _c { get; set; }

    public int? C
    {
        get
        {
            int retval;

            return !string.IsNullOrWhiteSpace(_c) && int.TryParse(_c, out retval) ? (int?) retval : null;
        }
    }
}

或者稍微好一点使用DataContractSerializer

[DataContract(Name="a")]
public class A1
{
    [DataMember(Name = "b")]
    public int? B { get; set; }

    [DataMember(Name = "c")]
    private string _c { get; set; }

    public int? C
    {
        get
        {
            int retval;

            return !string.IsNullOrWhiteSpace(_c) && int.TryParse(_c, out retval) ? (int?)retval : null;
        }
    }
}

虽然DataContractSerializer不支持属性,但如果这是一个问题,可以考虑其他方案。


感谢您的帖子。我知道你告诉我的内容。但是,我无法控制 XML 数据(这是来自外部服务)。而且服务提供商返回<c></c>,事实上应该返回 xs:Nil='true',所以我必须处理这个问题。 - Zé Carlos
你需要将C变成一个字符串,然后创建一个包装器属性,将反序列化的字符串解析为所需的整数。 - Phil
谢谢。你的测试应该是“String.IsNullOrEmpty”而不是“IsNullOrWhiteSpace”吧?您不需要执行转换 (int?)retval。但是,为了避免编译错误,我们需要将(int?)null强制转换。 - Zé Carlos
我猜你不需要使用 string.IsNullOrWhitespace 或 string.IsNullOrEmpty,因为 TryParse 已经足够了。是的,在使用 ?: 运算符时需要 (int?) 情况,这样两边都会计算为 (int?)。 - Phil
值得注意的是,如果您的类属性名称与XML元素名称完全匹配(在属性小写“c”通过属性映射到大写属性“C”之上),则通过XmlElement或DataMember属性重定向元素可能会导致冲突,如果您的目标已经隐式地通过名称匹配,则会发生这种情况。将[XmlIgnore]属性添加到目标元素可以解决此问题。 - John Spiegel

9
为了反序列化空标签,例如您示例中的'c':
    <foo>
        <b>2</b>
        <c></c>
    </foo>

我使用了这种方法。首先,使用LINQ从XML文件中删除空元素,然后将没有空标签的新文档反序列化到Foo类中。

    public static Foo ReadXML(string file)
    {
            Foo foo = null;
            XDocument xdoc = XDocument.Load(file);
            xdoc.Descendants().Where(e => string.IsNullOrEmpty(e.Value)).Remove();

            XmlSerializer xmlSer = new XmlSerializer(typeof(Foo));
            using (var reader = xdoc.Root.CreateReader())
            {
                foo = (Foo)xmlSer.Deserialize(reader);
                reader.Close();
            }
            if (foo == null)
                foo = new Foo();

            return foo;
    }

这将为您在缺少属性时提供默认值。

    foo.b = 2;
    foo.c = 0; //for example, if it's an integer

我从以下这些链接中汇总了信息:

删除空的XML标签

将XDocument用作XmlSerializer.Deserialize的源?


为什么不用 xdoc.Descendants().Where(e => e.IsEmpty || String.IsNullOrWhiteSpace(e.Value)) - stomy
你也可以使用 using (TextReader reader = new StringReader(xdoc.ToString())) - stomy
您真是救了我的一天! - Fluous
干得好。在2022年仍然有效。谢谢。 - Barry

0

null和空是两个不同的概念。

你需要使用两个属性:

[XmlElement("c")]
string CAsString { get; set; } = "";

public int? C 
{
    get 
    {
        if (string.IsNullOrWhiteSpace(CAsString)) return null;
        return int.Parse(CAsString);
    }
}

第一个可以保持私密,不会污染您的API。
第二个是只读的,因此在序列化过程中将被忽略。
当然,这仅适用于读取,如果需要写入,可以为C属性添加一个setter。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接