Gerwald Gerwald - 16 days ago 7
C# Question

Changing type of element in XML Serialization

I am having huge problems with XML serialization. I have two classes, both need to be serializeable. In the inherited class, I would like to change the serialization behavior, so that a string property gets serialized as complex type.

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);


I have tried different approaches, but I didn't find a way to change how the
NAME
element get's serialized.
With the example above, I get the error message:

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.

dbc dbc
Answer

The reason you get the error is that, during XmlSerializer code generation, the code generator doesn't understand that the two potential NAME elements on Cat will never be simultaneously serialized, so throws the exception.

Instead, you can apply XmlAnyElementAttribute to a virtual property returning an XElement, then manually create and return an appropriate XElement for the name for each class in the hierarchy:

[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));
        }
    }
}

Using the extension methods:

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;
    }
}

Which, for a List<Animal>, produces XML like this:

<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>
Comments