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

4

我在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元素序列化方式的方法。 使用上面的示例,我收到以下错误消息:

XML元素'NAME'来自名称空间''已经存在于当前范围中。使用XML属性指定另一个XML名称或名称空间。

2个回答

3
你出现错误的原因是,在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>

0

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

我将提供一个解决方案,但它涉及将Cat类序列化的类型和名称连接起来。如果不必序列化NameAndType类,则可以继续执行以下操作: 在Animal上,将Name设置为虚拟的; 在Cat上,将XmlIgnore设置为Name2。然后覆盖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 { } 
    }        
}

谢谢你的回答。我不能将Name设置为受保护,因为我还需要使用Animal类。这两个类都需要可序列化。 覆盖Name听起来不错,但如何返回NameAndType的两个属性呢? - Gerwald
检查编辑并告诉我是否适合。如果不适合,请尝试添加您期望的输出XML格式,以便我可以为您提供另一种解决方案。 - Gabriel Rainha

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