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

时间:2015-05-16 15:45:44

标签: c# .net xml-serialization

我在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.

2 个答案:

答案 0 :(得分:3)

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

相反,您可以将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>

答案 1 :(得分:0)

您在同一图表级别指定了两个具有相同名称的不同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 { } 
    }        
}