Home > Software engineering >  Deserialize XML with elements that differ by attribute
Deserialize XML with elements that differ by attribute

Time:09-21

I have this snippet of XML (actually it's XBRL, but that is based on XML)

<xbrl>
<context id="Context_Duration" />
<context id="Context_Instant_Begin" />
<context id="Context_Instant_End" />    
<ConstructionDepotSpecification>
    <ConstructionDepotLabel contextRef="Context_Duration">depot</ConstructionDepotLabel>
    <ConstructionDepotBalance contextRef="Context_Instant_Begin">45000</ConstructionDepotBalance>
    <ConstructionDepotBalance contextRef="Context_Instant_End">42000</ConstructionDepotBalance>
</ConstructionDepotSpecification> 
</xbrl>

(additional content and xml namespaces declarations removed for clarity)

I want to deserialize this to a class, but I'm not sure how to handle the ConstructionDepotBalance elements. If I define a property ConstructionDepotBalance it will just take the value of the first element, so I think I should create two properties instead, one for begin value and one for end value. So the class should look like this

[XmlRoot(ElementName = "xbrl")]
public partial class Taxonomy
{
    [XmlElement]
    public List<ConstructionDepotSpecification> ConstructionDepotSpecification { get; set; }
}

public partial class ConstructionDepotSpecification 
{
    public string ConstructionDepotLabel { get; set; }

    public long? ConstructionDepotBalanceBegin { get; set; }
    public long? ConstructionDepotBalanceEnd { get; set; }
}

So the element with attribute Context_Instant_Begin should be deserialized to ConstructionDepotBalanceBegin and the other element with attribute Context_Instant_End should be deserialized to ConstructionDepotBalanceEnd.

Is this possible to achieve? Should I use an IXmlSerializable implementation for this?

CodePudding user response:

My first approach:

You could parse the XML-String first and replace

[ConstructionDepotBalance contextRef="Context_Instant_Begin"]

with

[ConstructionDepotBalanceBegin]

(Same with ConstructionDepotBalanceEnd).

In the second step you deserialze the XML string.

CodePudding user response:

I experimented a bit with the IXmlSerializable interface and came up with this implementation:

public void ReadXml(XmlReader reader)
{
    reader.ReadStartElement();
    while (!reader.EOF)
    {
        var ctx = reader.GetAttribute("contextRef");
        if (ctx == "Context_Duration")
        {
            string propName = reader.Name;
            var propInfo = GetType().GetProperty(propName);
            Type propType = propInfo.PropertyType;
            if (propType.GenericTypeArguments.Length > 0)
            {
                propType = propType.GenericTypeArguments[0];
            }
            var value = reader.ReadElementContentAs(propType, null);
            propInfo.SetValue(this, value);
        }

        else if (ctx == "Context_Instant_Begin")
        {
            string propName = reader.Name   "Begin";
            var propInfo = GetType().GetProperty(propName);
            var value = reader.ReadElementContentAsLong();
            propInfo.SetValue(this, value);
        }

        else if (ctx == "Context_Instant_End")
        {
            string propName = reader.Name   "End";
            var propInfo = GetType().GetProperty(propName);
            var value = reader.ReadElementContentAsLong();
            propInfo.SetValue(this, value);
        }

        if (reader.NodeType == XmlNodeType.EndElement)
        {
            reader.ReadEndElement();
            break;
        }
    }
}

Not sure if it's the best solution for this problem, but for now it does what I want.

  • Related