【问题标题】:Is there a way to retain XML Attributes when serializing part of a class?序列化类的一部分时有没有办法保留 XML 属性?
【发布时间】:2017-08-06 21:02:44
【问题描述】:

我正在尝试仅序列化一个类的一部分。我已将 XML 属性添加到类成员,以便正确命名生成的 XML 标记以匹配规范,而不管我的属性被命名为什么。这在序列化主类时工作正常。但是,如果我只想序列化部分类,我会丢失 XML 属性并且名称会恢复为默认值。仅序列化类的一部分时,有没有办法保留 XML 属性?

[XmlRoot ("someConfiguration")]
public class SomeConfiguration
{
    [XmlArray("bugs")]
    [XmlArrayItem("bug")]
    public List<string> Bugs { get; set; }
}

当我序列化整个类时,我得到了这个(这正是我所期望的):

<someConfiguration>
  <bugs>
    <bug>Bug1</bug>
    <bug>Bug2</bug>
    <bug>Bug3</bug>
  </bugs>
</someConfiguration>

如果我尝试仅序列化类的“Bugs”部分,我会得到这个(注意更改标签名称的 XML 属性都被忽略):

<ArrayOfString>
  <string>Bug1</string>
  <string>Bug2</string>
  <string>Bug3</string>
</ArrayOfString>

我需要得到这个:

  <bugs>
    <bug>Bug1</bug>
    <bug>Bug2</bug>
    <bug>Bug3</bug>
  </bugs>

如何让部分类用上面的标签序列化?

或者更好的是,有没有办法在序列化一个简单的List&lt;object&gt; 时指定标签名称。这样您就可以使用&lt;ArrayOfobject&gt; 指定用于列表的标签而不是它,并指定用于数组项的标签而不是&lt;object&gt;

【问题讨论】:

    标签: c# wpf serialization xml-serialization xml-attribute


    【解决方案1】:

    有没有办法在序列化简单列表时指定标签名称。

    一般来说,根据具体情况,它可能会起作用。请参阅 MSDN 的 How to: Specify an Alternate Element Name for an XML Stream。那里的示例涉及覆盖特定字段的序列化,但也可以使用相同的技术来覆盖整个类型名称。

    但这对我来说似乎很麻烦。相反,为什么不直接处理序列化:

    private static string SerializeByLinqAndToString<T>(
        List<T> data, string rootName, string elementName)
    {
        XDocument document = new XDocument(
            new XElement(rootName, data.Select(s => new XElement(elementName, s))));
    
        return SaveXmlToString(document);
    }
    
    private static string SaveXmlToString(XDocument document)
    {
        StringBuilder sb = new StringBuilder();
    
        using (XmlWriter xmlWriter = XmlWriter.Create(sb,
            new XmlWriterSettings { Indent = true, OmitXmlDeclaration = true }))
        {
            document.Save(xmlWriter);
        }
    
        return sb.ToString();
    }
    

    这样调用:

    SomeConfiguration config = ...; // initialize as desired
    
    string result = SerializeByLinq(config.Bugs, "bug", "bugs");
    

    以上内容仅适用于字符串列表或类型列表,其中元素内容可以只是在类型实例上调用ToString() 的结果。

    在处理复杂类型时,使用 .NET 中成熟的序列化功能可能是值得的,但如果您只有一个简单的字符串列表,则 LINQ-to-XML 功能非常方便。

    如果您确实有更复杂的类型,您可以将每个列表元素转换为 DOM 的 XElement 并将其序列化:

    private static string SerializeByLinq<T>(
        List<T> data, string rootName, string elementName = null)
    {
        XDocument document = new XDocument(
            new XElement(rootName, data.Select(t =>
                ElementFromText(SerializeObject(t), elementName)
            )));
    
        return SaveXmlToString(document);
    }
    
    private static XElement ElementFromText(string xml, string name = null)
    {
        StringReader reader = new StringReader(xml);
        XElement result = XElement.Load(reader);
    
        if (!string.IsNullOrEmpty(name))
        {
            result.Name = name;
        }
    
        return result;
    }
    
    private static string SerializeObject<T>(T o)
    {
        XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
        StringWriter textWriter = new StringWriter();
    
        using (XmlWriter writer = XmlWriter.Create(textWriter,
            new XmlWriterSettings { Indent = true, OmitXmlDeclaration = true }))
        {
            xmlSerializer.Serialize(writer, o,
                new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty}));
        }
    
        return textWriter.ToString();
    }
    

    在第二个示例中,您可以省略子元素的元素名称,它只会使用已设置的类型(例如类型名称,或任何 [XmlRoot] 设置为)。

    【讨论】:

    • 我用“string”以外的类型尝试了这个,它显示了“bug”对象的“ToString()”表示,而不是 XML 表示。
    • @Curtis:嗯,是的。您的问题是关于序列化 string 值的列表,而不是其他对象。其他情况必然会更加复杂。在这些情况下,正如我所提到的,您可能会发现我提到的序列化覆盖技术是必需的。
    • 我尝试在传递给新 XElement 的 't' 上调用 serialize,但它最终将 替换为 &gt 等...
    • @Curtis:是的,这是意料之中的。 XElement 内容必须被保留,因此如果它看起来像 XML,则必须将其转义/更改为实体以确保它被保留。您必须扩展基本的 LINQ-to-XML 技术,以进一步将 T 对象序列化为要作为内容包含的 XElement 对象。以上仅适用于T,其中T.ToString() 产生所需的值(例如,stringint 等基本类型以及类似工作的用户定义类型)。
    • @Curtis:我很欣赏您编辑背后的想法,但是一旦您发现自己调用string.Replace() 作为处理XML 的一种方式,您通常会走错方向。更好的方法是生成 XML(例如,使用您拥有的 Serialize&lt;T&gt;() 方法),然后使用 XDocument 加载它,最后将该 DOM 复制到您正在生成的 DOM 中。如果我有时间,我会考虑在上面的答案中添加类似 that 的内容。
    【解决方案2】:

    只要把它扔出去,你就可以将 List 包装在一个自定义类中:

    [XmlRoot("config")]
    public class SomeConfiguration
    {
        [XmlElement("bugs")]
        public BugList Bugs { get; set; }
        [XmlElement("trees")]
        public TreeList Trees { get; set; }
    }
    
    [XmlRoot("bugs")]
    public class BugList 
    {
        [XmlElement("bug")]
        public List<string> Items = new List<string>();
    }
    
    [XmlRoot("trees")]
    public class TreeList
    {
        [XmlElement("tree")]
        public List<string> Items = new List<string>();
    }   
    

    这将允许您序列化各个列表,并且它们将按照您的预期植根。

    void Main()
    {
        var config = new SomeConfiguration
        {
            Bugs = new BugList { Items = { "Bug1", "Bug2" } },
            Trees = new TreeList { Items = { "Tree1", "Tree2" } }
        };
    
        // Your config will work as normal.
        Debug.WriteLine(ToXml(config)); // <config> <bugs>.. <trees>..</config>
    
        // Your collections are now root-ed properly.
        Debug.WriteLine(ToXml(config.Bugs)); // <bugs><bug>Bug1</bug><bug>Bug2</bug></bugs>
        Debug.WriteLine(ToXml(config.Trees)); // <trees><tree>Tree1</tree><tree>Tree2</tree></trees>
    }
    
    public string ToXml<T>(T obj)
    {
        var ser = new XmlSerializer(typeof(T));
        var emptyNs = new XmlSerializerNamespaces();
        emptyNs.Add("","");
        using (var stream = new MemoryStream())
        {
            ser.Serialize(stream, obj, emptyNs);
            return Encoding.ASCII.GetString(stream.ToArray());
        }
    }
    

    【讨论】:

      【解决方案3】:

      找到了一种“变通”的方法。而不是将 XMLArray 和 XMLArrayList 属性放在 List:

      [XmlRoot ("someConfiguration")]
      public class SomeConfiguration
      {
          [XmlArray("bugs")]
          [XmlArrayItem("bug")]
          public List<string> Bugs { get; set; }
      }
      

      在列表上放置一个 XmlElement 属性,该属性将指定要用于每个元素的标记,并且没有包装列表的标记。你的类标签实际上会为你做到这一点。

      [XmlRoot ("bugs")]
      public class SomeConfiguration
      {
          [XmlElement("bug")]
          public List<string> Bugs { get; set; }
      }
      

      当你序列化上面的时候,你最终会得到:

        <bugs>
          <bug>Bug1</bug>
          <bug>Bug2</bug>
          <bug>Bug3</bug>
        </bugs>
      

      【讨论】:

      • 以上如何让你序列化just列表?
      • 你是对的。这有点像黑客......但最终得到了我正在寻找的结果。我实际上更喜欢你的方法,因为它更通用,可以用来正确序列化类的一部分。我只需要弄清楚如何显示序列化的 而不是 t.ToString()。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多