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

时间:2017-12-04 20:40:31

标签: c# xml serialization xsd xmlserializer

我目前的任务是将数据发送到具有指定列表的奇怪方式的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#类来轻松地序列化结构。生成的位如下:

[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的另一方的轶事数据),正确/预期的xml应该如下所示(例如,包含两个项目的列表 - 为了说明目的添加注释内联): / p>

<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#类序列化为:

<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序列化该类。我通常不会为其他Web服务序列化合理的模式。但由于某种原因,我不能为我的生活弄清楚如何做到这一点(如果它甚至可能)。

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

2 个答案:

答案 0 :(得分:1)

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

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

  1. Serializing a list of KeyValuePair to XML显示了如何使用XmlSerializer 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(); } 来生成多个集合中交错的元素列表。

  2. polymorphic list functionality展示了如何使用Xml Sequence deserialization with RestSharp属性生成交错的元素列表。

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

    [XmlIgnore]

    注意:

    • 原始馆藏标有ValueWrapper<string>[] Values

    • 引入了一个代理多态数组属性<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:a="http://pia.com/xml/ns" exclude-result-prefixes="a"> <xsl:output method="xml" version="1.0" encoding="UTF-8" omit-xml-declaration="no" indent="yes"/> <xsl:template match="a:TransactionReferenceNumber"> <TxId> <xsl:value-of select="."/> <xsl:text>&#xa;</xsl:text> </TxId> </xsl:template> <xsl:template match="a:ExecutingEntityIDCode"> <ExctgPty> <xsl:value-of select="."/> <xsl:text>&#xa;</xsl:text> </ExctgPty> </xsl:template> </xsl:stylesheet> ,它可以包含多个类型,每个类型对应一个可能的元素名称。

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

    示例[XmlAnyElement]

答案 1 :(得分:1)

我将dbc的答案标记为正确,因为他的评论和答案是导致我最终实施的原因。为了完整起见(以及在将来遇到这种情况时提供某种文档),这就是我最终实现此功能的方式。

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

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类并将其放在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: https://stackoverflow.com/a/23897411/3744182
// 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>