Changing the Element Type in XML Serialization

I am having huge problems with XML serialization. I have two classes, both need to be serialized. In an inherited class, I would like to change the serialization behavior so that the string property is serialized as a 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've tried different approaches, but I haven't found a way to change how the element is created NAME

. In the above example, I am getting an error:

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.

+3


source to share


2 answers


The reason you get the error is because during code generation, the code XmlSerializer

generator does not understand that the two potential NAME

elements Cat

will never be serialized at the same time, so it throws an exception.

Instead, you can apply XmlAnyElementAttribute

to a return virtual property, XElement

then manually create and return an appropriate one 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 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 List<Animal>

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

      

+3


source


You are specifying two different XML elements with the same name at the same graph level, which is not valid.

I offer a solution, but it involves type and name concatenation to serialize the Cat class. If you don't need to serialize this NameAndType class, then go ahead: On Animal, set Name to virtual; In Cat, set XmlIgnore to Name2. Then override the name and return both Name2 properties as you think.

If you really need to serialize this class as it is, then I'm afraid you will have to do it with a different element name.



Edit: Example code:

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

      

0


source







All Articles