在序列化类的一部分时,有没有办法保留XML属性?

时间:2017-03-15 21:51:49

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

我正在尝试序列化一个类的一部分。我已经将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<object>时是否有指定标记名称的方法。这样您就可以使用<ArrayOfobject>指定用于列表的标记而不是指定用于数组项的标记而不是<object>

3 个答案:

答案 0 :(得分:2)

  

是否可以在序列化简单列表时指定标记名称。

通常,根据确切的情况,可能会使其工作。请参阅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])。< / p>

答案 1 :(得分:2)

只要把它扔出去,就可以包装List&lt;&gt;在自定义类中:

[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());
    }
}

答案 2 :(得分:0)

找到一个解决方案&#39;这样做的方式..而不是将XMLArray和XMLArrayList属性放在List&lt;&gt;上面:

[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>