如何使用动态标记名称将数组序列化为XML

时间:2018-05-18 16:27:39

标签: c# xml

我需要使用动态标记名称在C#上将对象数组序列化为XML。我创建了两个需要序列化为xml的类,但我无法弄清楚如何使用动态名称创建标记。

例如,我有以下类:

[System.Xml.Serialization.XmlRootAttribute(IsNullable = false)]
public class GeneralInformation
{

    private Info[] addInfoList;

    /// <remarks/>
    [System.Xml.Serialization.XmlArray("InfoList")]
    public Info[] AddInfoList
    {
        get
        {
            return this.addInfoList;
        }
        set
        {
            this.addInfoList = value;
        }
    }
}

public class Info
{
    private string infoMessage;

    /// <remarks/>
    [System.Xml.Serialization.XmlElement("InfoName")]
    public string InfoMessage
    {
        get
        {
            return this.infoMessage;
        }
        set
        {
            this.infoMessage = value;
        }
    }
}

如果我添加一些简单数据并将其序列化,我会得到:

<?xml version="1.0" encoding="utf-16"?>
<GeneralInformation xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <InfoList>
        <Info>
            <InfoName>Test1</InfoName>
        </Info>
        <Info>
            <InfoName>Test2</InfoName>
        </Info>
        <Info>
            <InfoName>Test3</InfoName>
        </Info>
    </InfoList>
</GeneralInformation>

但是我需要用数组的索引+1来枚举标签“Info”。这可能吗?结果看起来像这样。

<?xml version="1.0" encoding="utf-16"?>
<GeneralInformation xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <InfoList>
        <Info001>
            <InfoName>Test1</InfoName>
        </Info001>
        <Info002>
            <InfoName>Test2</InfoName>
        </Info002>
        <Info003>
            <InfoName>Test3</InfoName>
        </Info003>
    </InfoList>
</GeneralInformation>

注意我只需要将GeneralInformation序列化为XML,而不是反序列化。

3 个答案:

答案 0 :(得分:2)

使用XDocument:

Category

答案 1 :(得分:0)

<InfoList>元素的值最自然地表示为Dictionary<string, Info>,但不幸的是XmlSerializer does not support dictionaries

相反,由于您要收集数组中的Info个对象,因此您可以使用从this answer Deserialize XML with XmlSerializer where XmlElement names differ but have same content 的方法并序列化您的{{ 1}}数组通过[XmlAnyElement("InfoList")] public XElement代理属性,其中Info []实例通过构造嵌套的Info被序列化为命名元素。

首先,按如下方式定义XmlSerializer

GeneralInformation

然后,从Deserialize XML with XmlSerializer where XmlElement names differ but have same content逐字抓取[System.Xml.Serialization.XmlRootAttribute(IsNullable = false)] public class GeneralInformation { private Info[] addInfoList; /// <remarks/> [XmlIgnore] public Info[] AddInfoList { get { return this.addInfoList; } set { this.addInfoList = value; } } const string InfoPrefix = "Info"; const string InfoListPrefix = "InfoList"; [XmlAnyElement("InfoList")] [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)] public XElement AddInfoListXml { get { if (addInfoList == null) return null; return new XElement(InfoListPrefix, addInfoList .Select((info, i) => new KeyValuePair<string, Info>(InfoPrefix + (i + 1).ToString("D3", NumberFormatInfo.InvariantInfo), info)) .SerializeToXElements((XNamespace)"")); } set { if (value == null) { addInfoList = null; } else { addInfoList = value .Elements() .Where(e => e.Name.LocalName.StartsWith(InfoPrefix)) .DeserializeFromXElements<Info>() .Select(p => p.Value) .ToArray(); } } } } XmlKeyValueListHelper

XmlSerializerFactory

注意:

  • 通过在根级别应用于public static class XmlKeyValueListHelper { const string RootLocalName = "Root"; public static XElement [] SerializeToXElements<T>(this IEnumerable<KeyValuePair<string, T>> dictionary, XNamespace ns) { if (dictionary == null) return null; ns = ns ?? ""; var serializer = XmlSerializerFactory.Create(typeof(T), RootLocalName, ns.NamespaceName); var array = dictionary .Select(p => new { p.Key, Value = p.Value.SerializeToXElement(serializer, true) }) // Fix name and remove redundant xmlns= attributes. XmlWriter will add them back if needed. .Select(p => new XElement(ns + p.Key, p.Value.Attributes().Where(a => !a.IsNamespaceDeclaration), p.Value.Elements())) .ToArray(); return array; } public static IEnumerable<KeyValuePair<string, T>> DeserializeFromXElements<T>(this IEnumerable<XElement> elements) { if (elements == null) yield break; XmlSerializer serializer = null; XNamespace ns = null; foreach (var element in elements) { if (serializer == null || element.Name.Namespace != ns) { ns = element.Name.Namespace; serializer = XmlSerializerFactory.Create(typeof(T), RootLocalName, ns.NamespaceName); } var elementToDeserialize = new XElement(ns + RootLocalName, element.Attributes(), element.Elements()); yield return new KeyValuePair<string, T>(element.Name.LocalName, elementToDeserialize.Deserialize<T>(serializer)); } } public static XmlSerializerNamespaces NoStandardXmlNamespaces() { var ns = new XmlSerializerNamespaces(); ns.Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines. return ns; } public static XElement SerializeToXElement<T>(this T obj) { return obj.SerializeToXElement(null, NoStandardXmlNamespaces()); } public static XElement SerializeToXElement<T>(this T obj, XmlSerializerNamespaces ns) { return obj.SerializeToXElement(null, ns); } public static XElement SerializeToXElement<T>(this T obj, XmlSerializer serializer, bool omitStandardNamespaces) { return obj.SerializeToXElement(serializer, (omitStandardNamespaces ? NoStandardXmlNamespaces() : null)); } public static XElement SerializeToXElement<T>(this T obj, XmlSerializer serializer, XmlSerializerNamespaces ns) { var doc = new XDocument(); using (var writer = doc.CreateWriter()) (serializer ?? new XmlSerializer(obj.GetType())).Serialize(writer, obj, ns); var element = doc.Root; if (element != null) element.Remove(); return element; } public static T Deserialize<T>(this XContainer element, XmlSerializer serializer) { using (var reader = element.CreateReader()) { object result = (serializer ?? new XmlSerializer(typeof(T))).Deserialize(reader); return (T)result; } } } public static class XmlSerializerFactory { // To avoid a memory leak the serializer must be cached. // https://stackoverflow.com/questions/23897145/memory-leak-using-streamreader-and-xmlserializer // This factory taken from // https://stackoverflow.com/questions/34128757/wrap-properties-with-cdata-section-xml-serialization-c-sharp/34138648#34138648 readonly static Dictionary<Tuple<Type, string, string>, XmlSerializer> cache; readonly static object padlock; static XmlSerializerFactory() { padlock = new object(); cache = new Dictionary<Tuple<Type, string, string>, XmlSerializer>(); } public static XmlSerializer Create(Type serializedType, string rootName, string rootNamespace) { if (serializedType == null) throw new ArgumentNullException(); if (rootName == null && rootNamespace == null) return new XmlSerializer(serializedType); lock (padlock) { XmlSerializer serializer; var key = Tuple.Create(serializedType, rootName, rootNamespace); if (!cache.TryGetValue(key, out serializer)) cache[key] = serializer = new XmlSerializer(serializedType, new XmlRootAttribute { ElementName = rootName, Namespace = rootNamespace }); return serializer; } } } 属性的[XmlAnyElement("InfoList")]构造函数中指定"InfoList"名称,只有名为public XElement AddInfoListXml的元素及其内容将为通过代理人反序列化。

  • 原始<InfoList>属性必须标有AddInfoList,因此不会与代理人一起序列化。

  • 使用实现IXmlSerializable的自定义集合替换[XmlIgnore]数组将是解决此问题的另一种方法,但正确实现AddInfoList可能非常棘手。请参阅 How do you deserialize XML with dynamic element names? 以获取比此处所示更简单的示例,因为该问题中的元素仅包含文本内容。

您的示例XML现在可以反序列化并重新序列化,如示例.Net小提琴here所示,生成以下XML:

IXmlSerializable

答案 2 :(得分:0)

您可以使用自定义xml编写器。

public class CustomWriter : XmlTextWriter
{
    private int counter = 1;

    public CustomWriter(TextWriter writer) : base(writer) { }
    public CustomWriter(Stream stream, Encoding encoding) : base(stream, encoding) { }
    public CustomWriter(string filename, Encoding encoding) : base(filename, encoding) { }

    public override void WriteStartElement(string prefix, string localName, string ns)
    {
        if (localName == "Info")
        {
            base.WriteStartElement(prefix, localName + counter.ToString("0##"), ns);
            counter++;
        }
        else
        {
            base.WriteStartElement(prefix, localName, ns);
        }
    }
}

使用:

var xs = new XmlSerializer(typeof(GeneralInformation));

using (var writer = new CustomWriter(Console.Out))
{
    writer.Formatting = Formatting.Indented;
    xs.Serialize(writer, data);
}

命名空间:

using System;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;