使用XmlSerializer反序列化和序列化XML时保持排序

时间:2018-01-04 13:47:38

标签: c# xml xml-serialization xmlserializer

我有以下测试XML字符串:

<?xml version="1.0" encoding="UTF-8"?>
<test id="myid">
 <b>b1</b>
 <a>a2</a>
 <a>a1</a>
 <b>b2</b>
</test>

我使用这个类反序列化:

[XmlRoot(ElementName = "test")]
public class Test
{
    [XmlElement(ElementName = "a")]
    public List<string> A { get; set; }
    [XmlElement(ElementName = "b")]
    public List<string> B { get; set; }
    [XmlAttribute(AttributeName = "id")]
    public string Id { get; set; }
}

如果我现在要序列化对象,结果将是:

<?xml version="1.0" encoding="utf-16"?>
<test xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" id="myid">
  <a>a2</a>
  <a>a1</a>
  <b>b1</b>
  <b>b2</b>
</test>

有没有办法保持初始排序顺序? 我想我不能使用[XmlElementAttribute(Order = x)]因为订单不应该硬编码,但与初始xml完全相同。

我考虑过将订单属性添加到我的列表

[XmlRoot(ElementName="a")]
public class A 
{
    [XmlAttribute(AttributeName="order")]
    public string Order { get; set; }
    [XmlText]
    public string Text { get; set; }
}

[XmlRoot(ElementName="b")]
public class B 
{
    [XmlAttribute(AttributeName="order")]
    public string Order { get; set; }
    [XmlText]
    public string Text { get; set; }
}

[XmlRoot(ElementName="test")]
public class Test 
{
    [XmlElement(ElementName="a")]
    public List<A> A { get; set; }
    [XmlElement(ElementName="b")]
    public List<B> B { get; set; }
    [XmlAttribute(AttributeName="id")]
    public string Id { get; set; }
}

但序列化时我不知道如何订购。

2 个答案:

答案 0 :(得分:1)

您可以使用XmlSerializer执行此操作,方法是使用单个集合捕获<a><b>元素,并多次应用[XmlElement(Name, Type = typeof(...))]属性,每个所需的元素名称。因为您使用单个集合来反序列化两个元素,所以会自动保留顺序。但是,要使其工作,XmlSerializer必须能够在重新序列化时确定正确的元素名称。有两种方法可以实现这一点,如Choice Element Binding Support中所述:

  1. 如果集合包含多态项,则可以使用[XmlElementAttribute(String, Type)]构造函数将元素名称映射到具体项类型。例如,如果你有一系列可能是字符串或整数的元素,那么:

    <Things>
       <string>Hello</string>
       <int>999</int>
    </Things>
    

    这可以绑定到集合,如下所示:

    public class Things
    {
        [XmlElement(Type = typeof(string)),
        XmlElement(Type = typeof(int))]
        public List<object> StringsAndInts { get; set; }
    }
    
  2. 如果集合只包含单一类型的项,则元素名称可以在enum值的关联数组中进行编码,其中enum名称对应于元素名称和数组本身通过[XmlChoiceIdentifierAttribute]属性识别。

    有关详细信息,请参阅documentation examples

  3. 我发现选项#1比选项#2更容易使用。使用此方法,以下模型将反序列化并重新序列化您的XML,同时成功保留<a><b>元素的顺序:

    public abstract class StringElementBase
    {
        [XmlText]
        public string Text { get; set; }
    
        public static implicit operator string(StringElementBase element)
        {
            return element == null ? null : element.Text;
        }
    }
    
    public sealed class A : StringElementBase
    {
    }
    
    public sealed class B : StringElementBase
    {
    }
    
    [XmlRoot(ElementName = "test")]
    public class Test
    {
        [XmlElement("a", Type = typeof(A))]
        [XmlElement("b", Type = typeof(B))]
        public List<StringElementBase> Items { get; } = new List<StringElementBase>();
    
        [XmlIgnore]
        // For convenience, enumerate through the string values of the items.
        public IEnumerable<string> ItemValues { get { return Items.Select(i => (string)i); } }
    
        [XmlAttribute(AttributeName = "id")]
        public string Id { get; set; }
    }
    

    工作.Net fiddle

    有关使用[XmlChoiceIdentifier]将具有不同名称的元素序列反序列化为相同类型的c#对象的更多示例,请参阅例如herehere

答案 1 :(得分:-1)

不,基本上; XmlSerializer不支持这一点。如果您要使用该选项,则需要使用XDocumentXmlDocumentXmlReader手动编写。