以特定顺序在 C# 中将并行数组序列化为 XML

Serialize parallel arrays to XML in C# in a specific order

我目前的任务是将数据发送到 Web 服务,该服务指定列表的方式非常奇怪。我无法控制架构,我试图让对方更改架构的尝试失败了。所以我几乎坚持这个。

他们的架构定义方式是这样的(只包含相关位):

<xs:element name="Part">
  <xs:complexType>
    <xs:sequence>
      <xs:element name="List">
        <xs:complexType>
          <xs:sequence maxOccurs="4">
            <xs:element name="Name" type="xs:string" />
            <xs:element name="Data" type="xs:string" />
            <xs:element name="OtherData" type="xs:string" />
          </xs:sequence>
        </xs:complexType>
      </xs:element>
    </xs:sequence>
  </xs:complexType>
</xs:element>

我使用 xsd.exe 生成了一个 C# class 来轻松序列化结构。生成的bit如下:

[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.33440")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="the namespace")]
public partial class PartList {
    [System.Xml.Serialization.XmlElementAttribute("Name", Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public string[] Name { get; set; }

    [System.Xml.Serialization.XmlElementAttribute("Data", Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public string[] Data { get; set; }

    [System.Xml.Serialization.XmlElementAttribute("OtherData", Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public string[] OtherData  { get; set; }
}

是的,并行数组。现在,根据他们的文档(以及来自另一方通过其他方式生成 XML 的轶事数据),correct/expected xml 应该如下所示(例如,包含两个项目的列表- 为说明目的添加内联评论):

<Part xmlns="">
    <List>
        <Name>Some Test</Name> <!-- first item -->
        <Data>123131313</Data> <!-- first item -->        
        <OtherData>0.11</OtherData> <!-- first item -->        
        <Name>Other Lama</Name> <!-- second item -->
        <Data>331331313</Data> <!-- second item -->
        <OtherData>0.02</OtherData> <!-- second item -->
    </List>
</Part>

但是,我自动生成的 C# class 序列化为:

<Part xmlns="">
    <List>
        <Name>Marcos Test</Name> <!-- first item -->
        <Name>Pepe Lama</Name> <!-- second item -->
        <Data>123131313</Data> <!-- first item -->
        <Data>331331313</Data> <!-- second item -->
        <OtherData>0.11</OtherData> <!-- first item -->
        <OtherData>0.02</OtherData> <!-- second item -->
    </List>
</Part>

我的 XML 由于项目的排序而无法根据架构进行验证。我使用默认选项使用 System.Xml.Serialization.XmlSerializer 序列化 class。在为其他 Web 服务序列化合理的模式时,我通常不会遇到任何问题。但出于某种原因,我终其一生都无法弄清楚如何做到这一点(如果可能的话)。

有什么想法吗?我已经尝试使用 XmlOrderAttribute,但对结果的顺序没有影响。

这里的基本问题是 XmlSerializer 递归地降低对象图并将对象映射到 XML 个元素的块,但是你想 interleave 由某些对象生成的元素,即 public string[] 属性。不幸的是,这不是开箱即用的,仅使用 XML serializer attributes.

但是,您可以通过引入可以以所需格式序列化的代理 属性 来生成您需要的 XML。有两种不同的方法可以做到这一点,如下面两个问题所示:

  1. shows how to use the polymorphic list functionality of XmlSerializer 从多个集合中生成交错的元素列表。

  2. shows how to use an [XmlAnyElement] 属性 生成交错的元素列表。

例如,这里是方法 #1 的实现:

public partial class PartList
{
    [XmlIgnore]
    public List<string> Name { get; } = new List<string>();

    [XmlIgnore]
    public List<string> Data { get; } = new List<string>();

    [XmlIgnore]
    public List<string> OtherData { get; } = new List<string>();

    [System.Xml.Serialization.XmlElementAttribute("Name", typeof(Name), Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
    [System.Xml.Serialization.XmlElementAttribute("Data", typeof(Data), Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
    [System.Xml.Serialization.XmlElementAttribute("OtherData", typeof(OtherData), Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public ValueWrapper<string>[] Values
    {
        get
        {
            var list = new List<ValueWrapper<string>>();
            for (int i = 0, count = Math.Max(Name.Count, Math.Max(Data.Count, OtherData.Count)); i < count; i++)
            {
                if (i < Name.Count)
                    list.Add(new Name { Value = Name[i] });
                if (i < Data.Count)
                    list.Add(new Data { Value = Data[i] });
                if (i < OtherData.Count)
                    list.Add(new OtherData { Value = OtherData[i] });
            }
            return list.ToArray();
        }
        set
        {
            if (value == null)
                return;
            Name.AddRange(value.OfType<Name>().Select(v => v.Value));
            Data.AddRange(value.OfType<Data>().Select(v => v.Value));
            OtherData.AddRange(value.OfType<OtherData>().Select(v => v.Value));
        }
    }
}

public class Name : ValueWrapper<string> { }

public class Data : ValueWrapper<string> { }

public class OtherData : ValueWrapper<string> { }

public abstract class ValueWrapper<T> : ValueWrapper where T : IConvertible
{
    public override object GetValue() => Value; 

    [XmlText]
    public T Value { get; set; }
}

public abstract class ValueWrapper
{
    public abstract object GetValue();
}

备注:

  • 原始合集标有[XmlIgnore]

  • 引入了代理多态数组属性 ValueWrapper<string>[] Values,它可以包含多种类型,每个可能的元素名称一个。

  • 在创建和返回数组时,三个集合中的值交错排列。在数组setter中,值按类型拆分并定向到相关集合。

示例 fiddle.

我将 dbc 的回答标记为正确,因为他的评论和回答是我最终实现的原因。为了完整起见(并且为了以防万一我 运行 将来使用某种文档),这就是我最终实现此功能的方式。

我利用了 xsd.exe 将所有 class 生成为部分 class 的事实。这让我更容易添加新的 list/collection 属性,这些属性最终将被序列化,并且在架构更改时不会丢失更改。例如,要覆盖 RemoteServiceTypePart1 class 的 List 属性:

public partial class RemoteServiceTypePart1
{
    // Tell the Xml serializer to use "List" instead of "List_Override" 
    // as the element name.  
    [XmlAnyElement("List")]
    public XElement List_Override
    {
        get {
            var result = new List<XElement>(); 

            for (int i = 0; i < List.Name.Length; i++)
            {
                result.Add(new XElement("Name", List.Name[i]));
                result.Add(new XElement("Data", List.Data[i]));
                result.Add(new XElement("OtherData", List.OtherData[i]));
            }

            return new XElement("List", result.ToArray());
        }

        set { }
    }
}

现在唯一剩下的问题是如何将 [XmlIgnore] 属性添加到原始 属性,而不必在每次架构更改时都手动添加属性。为此,我使用了 XmlAttributeOverrides class 并将其放置在 xml 序列化逻辑所在的位置:

var xmlIgnoreAttr = new XmlAttributes { XmlIgnore = true };

var overrides = new XmlAttributeOverrides();
// Add an override for each class that requires properties to be ignored.
overrides.Add(typeof(RemoteServiceTypePart1), "List", xmlIgnoreAttr);
overrides.Add(typeof(RemoteServiceTypePart2), "List", xmlIgnoreAttr);
overrides.Add(typeof(RemoteServiceTypePart3), "List", xmlIgnoreAttr);

// In a real-world implementation, you will need to cache this object to 
// avoid a memory leak. Read: 
// Thanks to dbc for letting me know in the comments.
var ser = new XmlSerializer(typeof(RemoteServiceType), overrides);
// serialize, send xml, do whatever afterwards

就是这样。现在输出 XML 看起来像这样:

<RemoteServiceTypePart1 xmlns="">
  <List>
    <Name>Marcos Test</Name>
    <Data>123131313</Data>
    <OtherData>0.11</OtherData>
    <Name>Pepe Lama</Name>
    <Data>331331313</Data>
    <OtherData>0.02</OtherData>
  </List>
</RemoteServiceTypePart1>