更改XML序列化中的元素类型

本文关键字:元素 类型 XML 序列化 更改 | 更新日期: 2023-09-27 17:49:19

我在XML序列化方面遇到了很大的问题。我有两个类,都需要是可序列化的。在继承的类中,我想更改序列化行为,以便将字符串属性序列化为复杂类型。

public class Animal
{
    [XmlElement(ElementName = "NAME")]
    public string Name { get; set; }
    public virtual bool ShouldSerializeName() { return true; }
}
public class Cat : Animal
{
    public override bool ShouldSerializeName() { return false; }
    [XmlElement(ElementName = "NAME")]
    public NameAndType Name2 { get; set; }
}
public class NameAndType
{
    public string Name { get; set; }
    public string Type { get; set; }
}
...
var cat = new Cat {Name2 = new NameAndType {Name = "LittleCat"}};
new XmlSerializer(typeof(Cat)).Serialize(Console.Out, cat);

我尝试了不同的方法,但我没有找到一种方法来改变NAME元素的序列化方式。在上面的例子中,我得到了错误消息:

The XML element 'NAME' from namespace '' is already present in the current scope. Use XML attributes to specify another XML name or namespace for the element.

更改XML序列化中的元素类型

您得到错误的原因是,在XmlSerializer代码生成期间,代码生成器不理解Cat上的两个潜在的NAME元素永远不会同时序列化,因此抛出异常。

相反,您可以将XmlAnyElementAttribute应用于返回XElement的虚拟属性,然后为层次结构中的每个类的名称手动创建并返回适当的XElement:

[XmlInclude(typeof(Cat))]
public class Animal
{
    [XmlIgnore]
    public string Name { get; set; }
    [XmlAnyElement]
    public virtual XElement XmlName
    {
        get
        {
            return Name == null ? null : new XElement("NAME", Name);
        }
        set
        {
            Name = (value == null ? null : value.Value);
        }
    }
}
public class Cat : Animal
{
    // Must be cached as per https://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer%28v=vs.110%29.aspx
    static XmlSerializer nameSerializer;
    static Cat()
    {
        nameSerializer = new XmlSerializer(typeof(NameAndType), new XmlRootAttribute("NAME"));
    }
    [XmlIgnore]
    public NameAndType Name2 { get; set; }
    [XmlAnyElement]
    public override XElement XmlName
    {
        get
        {
            return (Name2 == null ? null : XObjectExtensions.SerializeToXElement(Name2, nameSerializer, true));
        }
        set
        {
            Name2 = (value == null ? null : XObjectExtensions.Deserialize<NameAndType>(value, nameSerializer));
        }
    }
}

使用扩展方法

public static class XObjectExtensions
{
    public static T Deserialize<T>(this XContainer element)
    {
        return element.Deserialize<T>(new XmlSerializer(typeof(T)));
    }
    public static T Deserialize<T>(this XContainer element, XmlSerializer serializer)
    {
        using (var reader = element.CreateReader())
        {
            object result = serializer.Deserialize(reader);
            if (result is T)
                return (T)result;
        }
        return default(T);
    }
    public static XElement SerializeToXElement<T>(this T obj)
    {
        return obj.SerializeToXElement(new XmlSerializer(obj.GetType()), true);
    }
    public static XElement SerializeToXElement<T>(this T obj, XmlSerializer serializer, bool omitStandardNamespaces)
    {
        var doc = new XDocument();
        using (var writer = doc.CreateWriter())
        {
            XmlSerializerNamespaces ns = null;
            if (omitStandardNamespaces)
                (ns = new XmlSerializerNamespaces()).Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
            serializer.Serialize(writer, obj, ns);
        }
        var element = doc.Root;
        if (element != null)
            element.Remove();
        return element;
    }
}

对于List<Animal>,生成如下XML:

<ArrayOfAnimal xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <Animal>
        <NAME>duck</NAME>
    </Animal>
    <Animal xsi:type="Cat">
        <NAME>
            <Name>Smokey</Name>
            <Type>Siamese</Type>
        </NAME>
    </Animal>
</ArrayOfAnimal>

您在同一图形级别上指定了两个具有相同名称的不同XML元素,这是不允许的。

我将提供一个解决方案,但它涉及到Cat类序列化的类型和名称的连接。如果不是必须序列化NameAndType类,那么继续:在Animal上,将Name设置为virtual;在Cat上,在Name2上设置XmlIgnore。然后重写Name,并按您喜欢的方式返回Name2的两个属性。

如果你真的需要序列化这个类,那么恐怕你必须用一个不同的elementName。

编辑:示例代码:
public class Animal
{
    [XmlElement(ElementName = "NAME")]
    public virtual string Name { get; set; }
    public virtual bool ShouldSerializeName() { return true; }
} 
public class Cat : Animal
{
    public override bool ShouldSerializeName() { return false; }
    [XmlIgnore]
    public NameAndType Name2 { get; set; }
    [XmlElement(ElementName = "NAME")]
    public override string Name 
    { 
        get
        {
            return String.Format("{0} [Type:{1}]", Name2.Name, Name2.Type); 
        }
        set { } 
    }        
}