当序列化类的一部分时,是否有保留XML属性的方法?

4

我正在尝试序列化类的部分内容。我已经为类成员添加了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个回答

2
有没有办法在序列化一个简单的List时指定标签名称?一般来说,根据具体情况,可能可以使它工作。请参阅MSDN的如何:指定XML流的替代元素名称。那里的示例涉及覆盖特定字段的序列化,但是也可能使用相同的技术来覆盖整个类型名称。但对我来说,这似乎太麻烦了。相反,为什么不显式处理序列化呢?
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
1
@Curtis:嗯,是的。你的问题是关于序列化一个string值列表,而不是其他对象。其他情况必然会更加复杂。在那些情况下,正如我所提到的,你可能会发现需要使用我提到的序列化重写技术。 - Peter Duniho
我尝试在传递给new XElement的“t”上调用serialize,但结果是将<替换为&lt,>替换为&gt等... - Curtis
1
@Curtis:是的,这是预期的。必须保留XElement内容,因此如果它看起来像XML,则必须转义/更改为实体以确保其被保留。您必须扩展基本的LINQ-to-XML技术,以进一步将T对象序列化为包含在内容中的XElement对象。上述仅适用于T.ToString()生成所需值(例如原始类型如stringint等和类似工作的用户定义类型)。 - Peter Duniho
@Curtis:我欣赏你编辑的想法,但是一旦你发现自己在使用string.Replace()来处理XML时,通常就会走向错误的方向。更好的方法是生成XML(例如使用你提到的Serialize<T>()方法),然后使用XDocument加载它,最后将该DOM复制到你正在生成的DOM中。如果我有时间,我会研究在上面的答案中添加类似于那样的东西。 - Peter Duniho
@Curtis:好的,我添加了那个例子。它可以做到你之前所做的事情,但不会出现字符串搜索和替换出错的风险。 - Peter Duniho

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

0
发现了一个“变通”的方法来解决它... 不要把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>

1
以上代码如何让你序列化“仅仅”列表? - Peter Duniho
你说得对。这有点像是一个hack...但最终达到了我想要的结果。实际上,我更喜欢你的方法,因为它更通用,可以用于正确地序列化类的一部分。我只需要想办法显示序列化的<t>而不是t.ToString()。 - Curtis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接