【问题标题】:Keep sort when deserialize and serialize XML using XmlSerializer使用 XmlSerializer 反序列化和序列化 XML 时保持排序
【发布时间】:2018-06-14 06:24:00
【问题描述】:

我有以下测试 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; }
}

但我不知道在序列化时如何按它们排序。

【问题讨论】:

  • 出于好奇,您是否有理由需要 XML 以特定顺序排列?在我看来,只有在将数据加载或反序列化回对象时才会使用这种排序。
  • @gmiley 我正在开发一个 Office VSTO 插件,它允许您添加自己的功能区选项卡,其中包含在 XML 中定义的 UI 元素。 xml 元素顺序表示功能区 UI 顺序(例如:保存按钮 - 关闭按钮或关闭按钮 - 保存按钮)
  • 如果你想一想 - Test 类不包含任何关于“原始”订单的信息,所以没有办法做到这一点。
  • 然后你会按照你的想法添加一个排序属性/元素并对集合执行排序。忘记你的 xml 的顺序。
  • @gmiley 仅对 a 和 b 进行排序不会使 b 的一个元素在 a 之前成为可能(如我的示例所示)。

标签: c# xml xml-serialization xmlserializer


【解决方案1】:

您可以使用XmlSerializer 执行此操作,方法是使用单个集合来捕获&lt;a&gt;&lt;b&gt; 元素并将[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 名称对应于元素名称,数组本身通过@ 标识987654324@ 属性。

    详情请见documentation examples

我发现选项 #1 比选项 #2 更易于使用。使用这种方法,以下模型将反序列化和重新序列化您的 XML,同时成功地保留 &lt;a&gt;&lt;b&gt; 元素的顺序:

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

【讨论】:

  • 感谢您在同一属性上使用多个 XmlElement 属性的建议。我现在可以在 Attribute 中添加 ElementName 以映射到自定义类。
【解决方案2】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-04-05
    • 1970-01-01
    • 1970-01-01
    • 2013-04-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多