This is a part of XML file I'm trying to de-serialize:

<entry>
...
<A:family type="user">
 <A:variationCount>7</A:variationCount>
 <A:part type="user">
    <title>94 LPS</title>
    <Voltage type="custom" typeOfParameter="Electrical Potential" units="V">120 V</Voltage>
    <Unit_Length displayName="Unit Length" type="custom" typeOfParameter="Length" units="mm">540</Unit_Length>
    <Unit_Height displayName="Unit Height" type="custom" typeOfParameter="Length" units="mm">222</Unit_Height>
    <Total_Cooling_Capacity displayName="Total Cooling Capacity" type="custom" typeOfParameter="Power" units="W">1758 W</Total_Cooling_Capacity>
    <Supply_Air_Width displayName="Supply Air Width" type="custom" typeOfParameter="Duct Size" units="mm">400 mm</Supply_Air_Width>
    <Supply_Air_Height displayName="Supply Air Height" type="custom" typeOfParameter="Duct Size" units="mm">150 mm</Supply_Air_Height>
    <Sensible_Cooling_Capacity displayName="Sensible Cooling Capacity" type="custom" typeOfParameter="Power" units="W">1348 W</Sensible_Cooling_Capacity>
    <Return_Air_Width displayName="Return Air Width" type="custom" typeOfParameter="Duct Size" units="mm">475 mm</Return_Air_Width>
    <Number_of_Poles displayName="Number of Poles" type="custom" typeOfParameter="Number of Poles">1</Number_of_Poles>
    <Load_Classification displayName="Load Classification" type="custom" typeOfParameter="Load Classification">Cooling</Load_Classification>
    <CFU_Material displayName="CFU Material" type="custom" typeOfParameter="Material">&lt;By Category&gt;</CFU_Material>
    <C2_Offset_1 displayName="C2 Offset 1" type="custom" typeOfParameter="Length" units="mm">108</C2_Offset_1>
    <C1_Offset_1 displayName="C1 Offset 1" type="custom" typeOfParameter="Length" units="mm">108</C1_Offset_1>
    <Apparent_Load displayName="Apparent Load" type="custom" typeOfParameter="Apparent Power" units="VA">50 VA</Apparent_Load>
 </A:part>
 <A:part type="user">
    ...
 </A:part>
 ...
</A:family>
</entry>

These are classes I'm using to de-serialize it:

[XmlType(AnonymousType = true)]
[XmlRoot("entry", Namespace="http://www.w3.org/2005/Atom")]
public class PartAtom
{
    ...
    [XmlElement("family", Namespace="urn:schemas-autodesk-com:partatom")]
    public Family Family { get; set; }
}

public class Family
{
    [XmlAttribute("type")]
    public string Type { get; set; }

    [XmlElement("variationCount")]
    public int VariationCount { get; set; }

    [XmlElement("part")]
    public FamilyType[] Parts { get; set; }
}

[XmlRoot(Namespace = "http://www.w3.org/2005/Atom")]
public class FamilyType 
{   
    [XmlAttribute("type")]
    public string Type { get; set; }

    [XmlElement("title")]
    public string Title { get; set; }

    [XmlArray]
    public Parameter[] Parameters { get; set; }
}

[XmlRoot(Namespace = "http://www.w3.org/2005/Atom")]
public struct Parameter
{
    [XmlAttribute("displayName")]
    public string Name { get; set; }

    [XmlAttribute("type")]
    public string Type { get; set; }

    [XmlAttribute("typeOfParameter")]
    public string DataType { get; set; }

    [XmlText]
    public string Value { get; set; }

    [XmlAttribute("units")]
    public string Units { get; set; }
}

I want the elements such as Voltage, Units_Length, Unit_Height, ... Apparent_Load etc to be de-serialized as instances of Parameters class. How can I accomplish something like that? Is it even possible?

UPDATE:

The parameters (XML elements below the title) are virtually infinite, so I can't contemplate all of them, so all the algorithms where I have to specify XmlElement names manually are unusable.

3

There are 3 answers

0
Culme On

One (ugly) way of doing it, could be to modify the XML before you de-serialize it. Just string-replace all of the "Voltage", "Unit_height" etc with "YourParameterClassName". Then they should all nicely deserialize, and I'm guessing that your "typeOfParameter" will still provide you with the same information that originally was present in the nodes before the replacement.

5
dmigo On

One solution might be multiple XmlElementAttribute with different names specified.Something like this:

 [XmlElement("Voltage")]
 [XmlElement("Unit_Height")]
 [XmlElement("Supply_Air_Height")]
 [XmlElement("CFU_Material")]
0
dbc On

If you don't want to implement IXmlSerializable on your class, one option would be to add a proxy property of XElement [] elements marked with [XmlAnyElement] and serialize the parameters from and to this list, fixing the names as required.

Assuming your XML has namespace declarations on the root element like the following that were omitted from the question:

<entry xmlns="http://www.w3.org/2005/Atom" xmlns:A="urn:schemas-autodesk-com:partatom">

Then the following should work:

[XmlType(Namespace = "http://www.w3.org/2005/Atom")]
public class FamilyType
{
    [XmlAttribute("type")]
    public string Type { get; set; }

    [XmlElement("title")]
    public string Title { get; set; }

    [XmlIgnore]
    public Parameter[] Parameters { get; set; }

    [XmlAnyElement]
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)]
    public XElement[] XmlParameters
    {
        get
        {
            return XmlKeyValueListHelper.SerializeAttributeNameValueList(Parameters, "name");
        }
        set
        {
            Parameters = XmlKeyValueListHelper.DeserializeAttributeNameValueList<Parameter>(value, "name");
        }
    }
}

Note that the serializer automatically deserializes the Title and Type properties rather than passing then to the AnyElement array. Then, in Parameter, I changed Name to DisplayName and added an ElementName property to hold the element name:

[XmlRoot(Namespace = "http://www.w3.org/2005/Atom")]
public struct Parameter
{
    [XmlAttribute("name")]
    public string ElementName { get; set; } // Added property.

    [XmlAttribute("displayName")]
    public string DisplayName { get; set; } // Changed from Name to DisplayName

    [XmlAttribute("type")]
    public string Type { get; set; }

    [XmlAttribute("typeOfParameter")]
    public string DataType { get; set; }

    [XmlText]
    public string Value { get; set; }

    [XmlAttribute("units")]
    public string Units { get; set; }
}

Using the extension and helper methods:

public static class XmlKeyValueListHelper
{
    public static XElement[] SerializeAttributeNameValueList<T>(IEnumerable<T> items, string nameAttributeName)
    {
        if (items == null)
            return null;
        var ns = new XmlSerializerNamespaces();
        ns.Add("", typeof(T).RootXmlElementNamespace());
        var query = items
            .Select(p => p.SerializeToXElement(ns))
            .Select(e =>
            {
                var attr = e.Attribute(nameAttributeName);
                e.Name = e.Name.Namespace + XmlConvert.EncodeLocalName((string)attr);
                attr.Remove();
                return e;
            });
        return query.ToArray();
    }

    public static T[] DeserializeAttributeNameValueList<T>(IEnumerable<XElement> elements, string nameAttributeName)
    {
        if (elements == null)
            return null;
        var query = elements
            .Select(e => new XElement(e)) // Do not modify the input values.
            .Select(e =>
            {
                e.Add(new XAttribute(nameAttributeName, XmlConvert.DecodeName(e.Name.LocalName)));
                e.Name = e.Name.Namespace + typeof(T).RootXmlElementName();
                return e;
            })
            .Select(e => e.Deserialize<T>());
        return query.ToArray();
    }
}

public static class XmlTypeExtensions
{
    public static string RootXmlElementName(this Type type)
    {
        var xmlRoot = type.GetCustomAttribute<XmlRootAttribute>();
        if (xmlRoot != null && !string.IsNullOrEmpty(xmlRoot.ElementName))
            return xmlRoot.ElementName;
        return type.Name;
    }

    public static string RootXmlElementNamespace(this Type type)
    {
        var xmlRoot = type.GetCustomAttribute<XmlRootAttribute>();
        if (xmlRoot != null && !string.IsNullOrEmpty(xmlRoot.Namespace))
            return xmlRoot.Namespace;
        return string.Empty;
    }
}

public static class XObjectExtensions
{
    static XmlSerializerNamespaces NoStandardXmlNamespaces()
    {
        var ns = new XmlSerializerNamespaces();
        ns.Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
        return ns;
    }

    public static XElement SerializeToXElement<T>(this T obj)
    {
        return obj.SerializeToXElement(null, NoStandardXmlNamespaces());
    }

    public static XElement SerializeToXElement<T>(this T obj, XmlSerializerNamespaces ns)
    {
        return obj.SerializeToXElement(null, ns);
    }

    public static XElement SerializeToXElement<T>(this T obj, XmlSerializer serializer, XmlSerializerNamespaces ns)
    {
        var doc = new XDocument();
        using (var writer = doc.CreateWriter())
            (serializer ?? new XmlSerializer(obj.GetType())).Serialize(writer, obj, ns);
        var element = doc.Root;
        if (element != null)
            element.Remove();
        return element;
    }

    public static T Deserialize<T>(this XContainer element)
    {
        return element.Deserialize<T>(new XmlSerializer(typeof(T)));
    }

    public static T Deserialize<T>(this XContainer element, XmlSerializer serializer)
    {
        using (var reader = element.CreateReader())
        {
            object result = serializer.Deserialize(reader);
            if (result is T)
                return (T)result;
        }
        return default(T);
    }
}

Still, easier than a custom IXmlSerializable.