6

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.

Community
  • 1
  • 1
Ben L
  • 1,302
  • 11
  • 23
  • 2
    Quite strange requirement. What if you have many properties of type Inner all with different names (InnerItem1, InnerItem2 etc)? They should all be serialized with the same names? – Evk Apr 22 '16 at 16:59
  • Thanks for the comment @Evk. For what I actually want to do, I will never have the structure you describe. – Ben L Apr 22 '16 at 18:19
  • 1
    Most likely you cannot do this with standard XmlSerializer, because as I said that's strange and unusual requirement, so developers of XmlSerializer class most likely could not plan for it :) – Evk Apr 22 '16 at 18:52
  • @Evk, [this answer](http://stackoverflow.com/a/5273502/930590) and [this answer](http://stackoverflow.com/a/6386271/930590) in the questions I mentioned do suggest this should work. As far as I can tell, I am doing the same thing. Any thoughts on what might be different? – Ben L Apr 22 '16 at 21:21
  • 1
    When you serialize, there is some root object (or array\list of root objects). On that root objects XmlType will work. However for children object it will have no effect, because for children objects property name is used as element name (which is reasonable). Or you can override property name with XmlElement attribute as suggested below. In both of your examples XmlType works for root objects only. Note - I mean root NOT in xml sense, but in a sense that's a top object\list of objects you are serializing. – Evk Apr 22 '16 at 21:29

3 Answers3

4

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
{
}
dbc
  • 104,963
  • 20
  • 228
  • 340
  • Any way to have a dynamic name generation dependent on the property name? (ie: instead of having [XmlElement("description")] to decorate a property named Descriptio, to have some dynamic code that does, say CurrentName.ToLowerCase() ) and Description will be description? – kkica Dec 04 '19 at 02:43
3

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
{
}
Zohar Peled
  • 79,642
  • 10
  • 69
  • 121
  • Thanks for your answer, but that's what I'm trying to avoid. I want to keep the information about how Outer should be renamed with the Outer class (because in reality, I intend Inner to be an abstract base class, each subclass of which will serialize to different element names). The other questions I referred to seem to suggest this is possible. – Ben L Apr 20 '16 at 13:35
  • I've tried using `XmlType` as suggested in the answers to the question you've linked to, but didn't manage to get it to change the property name in the xml output. perhaps someone else may find a working solution. – Zohar Peled Apr 21 '16 at 04:25
0

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.

Community
  • 1
  • 1
David Pine
  • 23,787
  • 10
  • 79
  • 107
  • Thanks for your answer, but that's what I'm trying to avoid. I've updated my question to clarify my requirements. I referred to the Q & A you mentioned in my question, as one of the answers there suggests that what I have done should work., However, I have been unable to reproduce it. – Ben L Apr 27 '16 at 15:33