We need to use a "web service" which communicates extremely ugly XML. It is not developed by us and there is zero chance to make its developers understand proper XML.
FYI, this web service also accepts the same kind of XML in a HTTP GET URL parameter (not the request body) - and the guys who developed it don't understand why that is a bad practice.
So, what is the fastest way to map an XML such as this:
<foo id="document">
<foo id="customer">
<bar name="firstname" value="Joe"/>
<bar name="lastname" value="Smith"/>
<foo id="address">
<bar name="city" value="New York"/>
<bar name="country" value="USA"/>
</foo>
</foo>
<bar name="somemoredata1" value="123"/>
<bar name="somemoredata2" value="abc"/>
</foo>
into classes like this:
public class Document
{
public Customer Customer { get; set; }
public int SomeMoreData1 { get; set; }
public string SomeMoreData2 { get; set; }
}
public class Customer
{
public Address Address { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Address
{
public string City { get; set; }
public string Country { get; set; }
}
using eg. XML Serializer attributes or any other way that needs as little boilerplate code as possible.
I made up the foo
and bar
element names, but the structure of the XML I need to parse is based on the exact same convention.
I could of course implement IXmlSerializable
manually in these classes or just make Foo
and Bar
classes and use those with the XmlSerializer
, but none of these options seem to be a good solution.
You can't do it with XML serializer attributes: there is just no way to make it take a field name out of a specified attribute. You will have to deserialize manually (possibly generating the boilerplate) or pre-process the XML — a simple XSLT along the following lines will do the trick:
<xsl:template match="foo">
<xsl:element name="{@id}">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="bar">
<xsl:element name="{@name}">
<xsl:value-of select="@value"/>
</xsl:element>
</xsl:template>
Update: for the reverse transformation:
<xsl:template match="*[count(child::text())=1]">
<bar value="{text()}" name="{local-name()}"/>
</xsl:template>
<xsl:template match="*">
<foo id="{local-name()}">
<xsl:apply-templates/>
</foo>
</xsl:template>