C# Xml序列化,集合和根元素

12

我的应用程序将对象序列化为流。 这是我所需要的示例:

<links>
  <link href="/users" rel="users" />
  <link href="/features" rel="features" />
</links>

在这种情况下,该对象是一个'links'对象的集合。

-----------第一个版本

起初我使用了DataContractSerializer,但是你无法将成员序列化为属性 (来源)

以下是该对象:

[DataContract(Name="link")]
public class LinkV1
{
    [DataMember(Name="href")]
    public string Url { get; set; }

    [DataMember(Name="rel")]
    public string Relationship { get; set; }
}

这里是结果:

<ArrayOflink xmlns:i="...." xmlns="...">
  <link>
    <href>/users</href>
    <rel>users</rel>
  </link>
  <link>
    <href>/features</href>
    <rel>features</rel>
  </link>
</ArrayOflink>

----------- 第二个版本

好吧,不是我想要的,所以我尝试了经典的 XmlSerializer,但是...哦不,如果根元素是集合,你不能指定根元素和集合元素的名称...

这是代码:

[XmlRoot("link")]
public class LinkV2
{
    [XmlAttribute("href")]
    public string Url { get; set; }

    [XmlAttribute("rel")]
    public string Relationship { get; set; }
}

这是结果:

<ArrayOfLinkV2>
  <LinkV2 href="/users" rel="users" />
  <LinkV2 href="/features" rel="features" />
  <LinkV2 href="/features/user/{keyUser}" rel="featuresByUser" />
</ArrayOfLinkV2>

----------- 第三个版本

使用 XmlSerializer + 根元素:

[XmlRoot("trick")]
public class TotallyUselessClass
{
    [XmlArray("links"), XmlArrayItem("link")]
    public List<LinkV2> Links { get; set; }
}

并且它的结果为:

 <trick>
  <links>
    <link href="/users" rel="users" />
    <link href="/features" rel="features" />
    <link href="/features/user/{keyUser}" rel="featuresByUser" />
  </links>
</trick>

不错,但我不希望根节点是那个!我想让我的集合成为根节点。

以下是限制条件:

  • 序列化代码是通用的,它适用于任何可序列化的内容
  • 反向操作(反序列化)也必须正常工作
  • 我不想通过正则表达式来处理结果(我直接将其序列化到输出流中)

现在有哪些解决方案:

  1. 编写自己的XmlSerializer
  2. 在 XmlSerializer 处理集合时进行小技巧(我尝试过找到一个 XmlRootElement 并将其复数形式以生成自己的 XmlRootAttribute,但这会导致反序列化时出现问题+项目名称仍然保留类名称)

有什么想法吗?

真正困扰我的是这个问题看起来非常非常简单...


我会作弊,将其序列化“按原样”,然后对生成的 XML 应用 XSL 转换以将其转换为所需的格式。或者,您是否看过 [MessageContract]?它比 DataContract 提供了更多的输出自定义选项。 - StuartLC
XSL作弊不是一个选项,因为序列化器直接写入流中...感谢[MessageContract]的线索,我今天学到了一些东西^^然而,它与WCF密切相关,不允许精细控制简单的XML序列化(我可能错了,只是发现了这个属性)。 - Mose
3个回答

9

好的,这是我的最终解决方案(希望能对某些人有所帮助),可以序列化普通数组、List<>、HashSet<>等。

为了实现这一点,我们需要告诉序列化器要使用哪个根节点,这有点棘手...

1)在可序列化的对象上使用“XmlType”

[XmlType("link")]
public class LinkFinalVersion
{
    [XmlAttribute("href")]
    public string Url { get; set; }

    [XmlAttribute("rel")]
    public string Relationship { get; set; }
}

2) 编写一个“智能根检测器”方法,该方法将返回XmlRootAttribute。

private XmlRootAttribute XmlRootForCollection(Type type)
{
    XmlRootAttribute result = null;

    Type typeInner = null;
    if(type.IsGenericType)
    {
        var typeGeneric = type.GetGenericArguments()[0];
        var typeCollection = typeof (ICollection<>).MakeGenericType(typeGeneric);
        if(typeCollection.IsAssignableFrom(type))
            typeInner = typeGeneric;
    }
    else if(typeof (ICollection).IsAssignableFrom(type)
        && type.HasElementType)
    {
        typeInner = type.GetElementType();
    }

    // yeepeeh ! if we are working with a collection
    if(typeInner != null)
    {
        var attributes = typeInner.GetCustomAttributes(typeof (XmlTypeAttribute), true);
        if((attributes != null)
            && (attributes.Length > 0))
        {
            var typeName = (attributes[0] as XmlTypeAttribute).TypeName + 's';
            result = new XmlRootAttribute(typeName);
        }
    }
    return result;
}

3) 将XmlRootAttribute推入序列化程序

// hack : get the XmlRootAttribute if the item is a collection
var root = XmlRootForCollection(type);
// create the serializer
var serializer = new XmlSerializer(type, root);

我告诉过你这很棘手 ;)

为了改进这个问题,你可以:

A)创建一个XmlTypeInCollectionAttribute来指定自定义根名称(如果基本的复数形式不符合你的需求)

[XmlType("link")]
[XmlTypeInCollection("links")]
public class LinkFinalVersion
{
}

B)如果可能的话,可以将XmlSerializer缓存(例如在静态字典中)。

在我的测试中,实例化一个没有XmlRootAttributes的XmlSerializer需要3毫秒。 如果指定了XmlRootAttribute,则需要约80毫秒(只是为了自定义根节点名称!)


4

XmlSerializer应该能够满足您的需求,但它高度依赖于初始结构和设置。我在自己的代码中使用它来生成非常相似的东西。

public class Links<Link> : BaseArrayClass<Link> //use whatever base collection extension you actually need here
{
    //...stuff...//
}

public class Link
{
    [XmlAttribute("href")]
    public string Url { get; set; }

    [XmlAttribute("rel")]
    public string Relationship { get; set; }
}

现在,对于Links类的序列化应该正好生成您要查找的内容。
XmlSerializer的问题在于当你给它泛型时,它会返回泛型。List在其中某个地方实现了Array,因此序列化结果几乎总是ArrayOf<X>。为了避免这种情况,您可以命名属性或类根。从您的示例中,最接近您所需的可能是第二个版本。我假设您尝试直接序列化一个对象List Links。这是不起作用的,因为您没有指定根节点。现在,您可以在这里找到类似的方法。在这个方法中,他们在声明序列化程序时指定了XmlRootAttribute。您的代码应该像这样:
XmlSerializer xs = new XmlSerializer(typeof(List<Link>), new XmlRootAttribute("Links"));

你的解决方案非常接近最终解决方案,但我不能直接在XmlSerializer构造函数中指定根属性名称(序列化代码在其他地方),我尝试通过序列化来“推断”它,这对于根节点有效,但对于元素无效。Mike提供了一个我不知道的“XmlType”属性的解决方案。 - Mose
组合解决方案位。耶。虽然我很高兴能帮忙 =D。 - Nevyn

1

给你...

 class Program
{
    static void Main(string[] args)
    {

        Links ls = new Links();
        ls.Link.Add(new Link() { Name = "Mike", Url = "www.xml.com" });
        ls.Link.Add(new Link() { Name = "Jim", Url = "www.xml.com" });
        ls.Link.Add(new Link() { Name = "Peter", Url = "www.xml.com" });

        XmlSerializer xmlSerializer = new XmlSerializer(typeof(Links));

        StringWriter stringWriter = new StringWriter();

        xmlSerializer.Serialize(stringWriter, ls);

        string serializedXML = stringWriter.ToString();

        Console.WriteLine(serializedXML);

        Console.ReadLine();
    }
}

[XmlRoot("Links")]
public class Links
{
    public Links()
    {
        Link = new List<Link>();
    }

    [XmlElement]
    public List<Link> Link { get; set; }
}

[XmlType("Link")]
public class Link
{
    [XmlAttribute("Name")]
    public string Name { get; set; }


    [XmlAttribute("Href")]
    public string Url { get; set; }

}

谢谢你的回答。你的解决方案有一些问题——1)不像应该那样工作,因为元素没有被重命名(你作弊了,你把类型称为“Link”,所以输出元素是<Link ...>),你可以通过在XmlElement节点中指定元素名称来解决这个问题——2)我更喜欢避免创建一个包含结构,因为它会对其他序列化格式造成问题(相同的对象用于多个序列化格式)——但是你给了我最终解决方案的一部分:谢谢! - Mose

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