简体   繁体   中英

Rename class when serializing to XML

I'm trying to serialize the Outer class shown below, and create an XElement from the serialized XML. It has a property which is of type Inner . I'd like to change the name of both Inner (to Inner_X ) and Outer (to Outer_X ).

class Program
{
    static void Main(string[] args)
    {
        using (MemoryStream memoryStream = new MemoryStream())
        {
            using (TextWriter streamWriter = new StreamWriter(memoryStream))
            {
                var xmlSerializer = new XmlSerializer(typeof(Outer));

                xmlSerializer.Serialize(streamWriter,  new Outer());

                XElement result = XElement.Parse(Encoding.ASCII.GetString(memoryStream.ToArray()));
            }
        }
    }
}

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

    public Inner InnerItem { get; set; }
}

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

This creates an XElement which looks like this:

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

What I would like is:

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

I want to keep the information about how a class should be renamed with that class. I thought I could do this with the XmlType attribute. However, this is ignored and the property name is used instead.

I've looked here and here , amongst other places, and feel like this should work. What am I missing?

Clarification

By "keep(ing) the information about how a class should be renamed with that class" , what I mean is that the term Inner_X should only appear in the Inner class. It should not appear at all in the Outer class.

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

When XmlSerializer serializes a type, the type itself controls the names of the elements created for its properties. Ie 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 (ie 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
{
}

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM