Rename class when serializing to XML

 ̄綄美尐妖づ 提交于 2019-11-28 01:19:35

When XmlSerializer serializes a type, the type itself controls the names of the elements created for its properties. I.e. the property name becomes the element name, unless overridden statically by XmlElementAttribute.ElementName. XmlTypeAttribute.TypeName generally only controls the element name when an instance of the type to which it is applied is not being serialized as the property of some containing type -- for instance, when it is the root element, or when it is contained in a collection that is being serialized with an outer container element. This design avoids name collisions in cases where there are multiple properties of the same type within a given type.

However, there is an exception in the case of polymorphic property types. For these, XmlSerializer has an option to use the XML type name of each of the possible polymorphic types as the element name, thereby identifying the actual c# type from which the element was created. To enable this functionality, one must add multiple [XmlElement(typeof(TDerived))] attributes to the property, one for each possible type TDerived.

You can use this capability to generate the XML you require by introducing a psuedo-polymorphic proxy property:

[XmlType("Outer_X")]
public class Outer
{
    public Outer()
    {
        this.InnerItem = new Inner();
    }

    [XmlIgnore]
    public Inner InnerItem { get; set; }

    [XmlElement(typeof(Inner))]
    [XmlElement(typeof(object))]
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)]
    public object InnerItemXmlProxy
    {
        get
        {
            return InnerItem;
        }
        set
        {
            InnerItem = (Inner)value;
        }
    }
}

Then the output is as you require:

<Outer_X xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <Inner_X />
</Outer_X>

Prototype fiddle.

However, as @evk commented, if your Outer class contains multiple properties of the same type, this cannot be done.

One other option to think about: if you simply don't want to manually duplicate the "Inner_X" type name strings in multiple locations (i.e. in both the [XmlType(string name)] and [XmlElement(string name)] attributes) you could centralize the type names by making them be public const:

[XmlType(Outer.XmlTypeName)]
public class Outer
{
    public const string XmlTypeName = "Outer_X";

    public Outer()
    {
        this.InnerItem = new Inner();
    }

    [XmlElement(Inner.XmlTypeName)]
    public Inner InnerItem { get; set; }
}

[XmlType(Inner.XmlTypeName)]
public class Inner
{
    public const string XmlTypeName = "Inner_X";
}

Update

I just noticed your comment I intend Inner to be an abstract base class, each subclass of which will serialize to different element names. If this is the case, then XmlSerializer can indeed be made to use the XML type name as the element name -- but only when it can determine statically that the property type is actually polymorphic due to the presence of multiple [XmlElement(typeof(TDerived))] attributes. Thus the following classes will generate the XML you require:

[XmlType("Outer_X")]
public class Outer
{
    public Outer()
    {
        this.InnerItem = new InnerX();
    }

    [XmlElement(typeof(InnerX))]
    [XmlElement(typeof(Inner))] // Necessary to inform the serializer of polymorphism even though Inner is abstract.
    public Inner InnerItem { get; set; }
}

public abstract class Inner
{
}

[XmlType("Inner_X")]
public class InnerX : Inner
{
}

You need to set the element name of the property, not the xml type of the inner class. Try this:

[XmlType("Outer_X")]
public class Outer
{
    public Outer()
    {
        this.InnerItem = new Inner();
    }

    [XmlElement("Inner_X")]
    public Inner InnerItem { get; set; }
}


public class Inner
{
}
David Pine

This is extremely simple to achieve. You need to use the XmlRootAttribute for the class and the XmlElementAttribute for the members as explained here on MSDN.

[XmlRoot(ElementName = "Outer_X")]
public class Outer
{    
    [XmlElement(ElementName = "Inner_X")]
    public Inner InnerItem { get; set; } = new Inner();
}

public class Inner { }

I have created a working .NET Fiddle to exemplify this. This SO Q & A seemed to address this similar concern. Finally, when decoding the XML to a string you should probably use a different encoding, no? According to this, strings are UTF-16 encoded -- not a big deal, but figured I'd call attention to it.

The fiddle I shared results in the following XML:

<Outer_X xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Inner_X />
</Outer_X>

Update

After you updated your question with the clarification, I now understand what you'd asking. Unfortunately, (to my knowledge) this cannot be controlled as you desire via attributes. You'd have to either create your own XML serializer / deserializer or accept the fact that there are limitations with the attribute support.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!