Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XmlSerializer: serializing a class property as an attribute of a custom subelement

I'm using an XmlSerializer. My class:

[Serializable]
[XmlRoot(ElementName="MyClass")]
public class MyClass
{
    public string Value;
}

I would like to serialize it so that Value ends up as an attribute of a subelement named (for instance) "Text".

Desired outcome:

<MyClass>
    <Text Value="3"/>
</MyClass>

But NOT (which would be the effect of marking Value as an XmlAttribute)

<MyClass Value="3">
</MyClass>

And NOT (which would be the effect of marking Value as an XmlElement):

<MyClass>
    <Value>3</Value>
</MyClass>

How do I achieve this?

I am aware that I could change the type of Value from string to another serializable custom class.

Unfortunately, I have plenty of such properties, so I'd need to create dozens of tiny classes.

Is there any quicker solution?


EDIT:

In response to your comments:

  • No, not every property has to be serialized to a subelement named "Text". Subelement's name is unique and unambiguous.

  • Sample output XML:

    <visibility>
        <site visible="yes"/>
        <comparator visible="no"/>
        <expiration days="7"/>
        <comment>blahblahblah</comment>
    <visibility>
    
  • Sample class:

!

[XmlRoot(ElementName="Visibility")]
public class Visibility
{
    [XPath("/site@visible")] // if only this was possible!
    public string OnSite
    {
        get { return SiteVisible ? "yes" : "no"; }
    }

    [XPath("/comparator@visible")] // as above...
    public string InComparator
    {
        get { return ComparatorVisible ? "yes" : "no"; }
    }

    [XmlIgnore]
    public bool SiteVisible;
    [XmlIgnore]
    public bool ComparatorVisible;

    [XPath("/expiration@days")] // as above...
    public int ExpiresAfterDays; 

    [XmlElement("comment")] // this is easy
    public string Comment;
}
like image 249
Konrad Morawski Avatar asked Feb 06 '12 11:02

Konrad Morawski


People also ask

Why do we use XmlSerializer class?

Serialization/ De-serialization allow communication with another application by sending and receiving data. With XmlSerializer, you can control how objects are encoded into XML. Call the Serialize method with the parameters of the StreamWriter and object to serialize.

How does the XmlSerializer work C#?

The XmlSerializer creates C# (. cs) files and compiles them into . dll files in the directory named by the TEMP environment variable; serialization occurs with those DLLs. These serialization assemblies can be generated in advance and signed by using the SGen.exe tool.

How can you modify control the XML that is being generated by XmlSerializer?

By applying the XmlRootAttribute, you can control the XML stream generated by the XmlSerializer. For example, you can change the element name and namespace.

Which of the following attribute controls XML Serialisation of the attribute target as an XML root element?

Public class declarations. Controls XML serialization of the attribute target as an XML root element.


2 Answers

Without changing the type of Value I think it's not possible. You can add the attribute XmlElement(ElementName="Text") on Value but you will obtain a result similar to this:

<MyClass> 
    <Text>3</Text> 
</MyClass> 

Edited: Another solution can involve XSLT transformation: you can generate the xml using .Net serialization and after apply a xml transformation.

XslTransform myXslTransform = new XslTransform();
myXslTransform.Load(xsltDoc);
myXslTransform.Transform(sourceDoc, resultDoc);

The trasformation of my example should be something like this:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="/">
    <root>
        <xsl:apply-templates/>
    </root>
    </xsl:template>
    <xsl:template match="MyClass">
        <MyClass>
            <Text>
               <xsl:attribute name="Value">
                    <xsl:value-of select="Text"/>
               </xsl:attribute>
            </Text>
        </MyClass>
    </xsl:template>
</xsl:stylesheet>
like image 59
Massimo Zerbini Avatar answered Sep 29 '22 13:09

Massimo Zerbini


For this sort of flexibility, you should really think about implementing IXmlSerializable as this gives you much more control:

[XmlRoot("visibility")]
public class Visibility : IXmlSerializable
{
    public string Site;
    public string Comparator;
    public int Expiration;
    public string Comment;

    public XmlSchema GetSchema()
    {
        throw new NotImplementedException();
    }

    public void ReadXml(XmlReader reader)
    {
        // implement me if you want to deserialize too.
        throw new NotImplementedException();
    }

    public void WriteXml(XmlWriter writer)
    {
        WriteProperty(writer, "site", "visible", Site);
        WriteProperty(writer, "comparator ", "visible", Comparator);
        WriteProperty(writer, "expiration ", "days", Expiration);

        if (!string.IsNullOrEmpty(Comment))
        {
            writer.WriteElementString("comment", Comment);
        }
    }

    private void WriteProperty<T>(XmlWriter writer, string elementName, string attibuteName, T value)
    {
        if (value != null)
        {
            writer.WriteStartElement(elementName);
            writer.WriteAttributeString(attibuteName, value.ToString());
            writer.WriteEndElement();
        }
    }
}

Obviously, there is bit of manual work here, but it does allow you to keep all the serialization code in one place, rather than having a proliferation of smaller classes.

The example above only implements serialization - you'd need to write an equivalent deserialize implementation if you need to deserialize from xml to your type.

like image 39
Rob Levine Avatar answered Sep 29 '22 11:09

Rob Levine