Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Xml Deserialization - Merging two elements into a single List<T> object

I have an XML document, and using deserialization, is there a way to combine two elements into one object?

XML example:

<Parameter1>3</Parameter1>
<Parameter2>4</Parameter2>

I want to create a list (of type Parameter) that contains both items, 3 and 4.

I've tried using XmlArrayItem such as:

[XmlArrayItem("Parameter1")]
[XmlArrayItem("Parameter2")]
[XmlArray]
public Parameter[] Parameters; // have also tried this as public List<Parameter> Parameters = new List<Parameter>();

I've tried using XmlElements (but I can't figure out how to combine them):

[XmlElement("Parameter1")]
public List<Parameter> Parameters = new List<Parameter>();

Is there any way to do this without just creating two separate lists and combining them at a later point?

Please note that changing the XML format is not an option.

like image 727
Natalie E Avatar asked May 18 '16 18:05

Natalie E


1 Answers

Your XML has a schema that includes a choice element. A choice element indicates that one of a fixed set of elements -- <Parameter1> and <Parameter2> in your case -- will occur in the XML. XmlSerializer supports choice elements as is explained in Choice Element Binding Support:

If individual choice elements' types differ along with their names, Xsd.exe applies only XmlElementAttribute attributes to a public member. If they differ only by name, Xsd.exe applies an XmlChoiceIdentifierAttribute in addition, and adds extra logic for making the choice.

Thus, you have the following options to deserialize your XML:

  1. Subclass your Parameter class and specify different types for each element name, using [XmlElementAttribute(String, Type)]. The specific Parameter subclass instantiated would thereby capture the XML element name.

    I.e. you could do:

    public abstract class Parameter
    {
        [XmlText]
        public string Value { get; set; } // Could be int if you prefer.
    }
    
    public class Parameter1 : Parameter
    {
    }
    
    public class Parameter2 : Parameter
    {
    }
    
    [XmlType("Root")]
    public class RootObject
    {
        [XmlElement("Parameter1", typeof(Parameter1))]
        [XmlElement("Parameter2", typeof(Parameter2))]
        public Parameter[] Parameters { get; set; }
    }
    
  2. If you want to use the same Parameter type to deserialize both <Parameter1> and <Parameter2> elements, you must introduce an ancillary XmlChoiceIdentifierAttribute array to capture the XML element name:

    public class Parameter
    {
        [XmlText]
        public string Value { get; set; }
    }
    
    [XmlType("Root")]
    public class RootObject
    {
        [XmlElement("Parameter1", typeof(Parameter))]
        [XmlElement("Parameter2", typeof(Parameter))]
        [XmlChoiceIdentifier("ParametersElementName")]
        public Parameter[] Parameters { get; set; }
    
        [XmlIgnore]
        public ParametersChoiceType[] ParametersElementName { get; set; }
    }
    
    [XmlType(IncludeInSchema = false)]
    public enum ParametersChoiceType
    {
        Parameter1,
        Parameter2,
    }
    

    After deserialization, the ParametersElementName array will have the same number of entries as the Parameters array, and the enum values therein will indicate the XML element name actually encountered for each parameter.

  3. As a variation of option 2, if you do not need to capture the XML element name and just want to deserialize the values, you could create a "fake" choice array property as follows:

    [XmlType("Root")]
    public class RootObject
    {
        [XmlElement("Parameter1", typeof(Parameter))]
        [XmlElement("Parameter2", typeof(Parameter))]
        [XmlChoiceIdentifier("ParametersElementName")]
        public Parameter[] Parameters { get; set; }
    
        [XmlIgnore]
        public ParametersChoiceType[] ParametersElementName
        {
            get
            {
                if (Parameters == null)
                    return null;
                return Parameters.Select(p => ParametersChoiceType.Parameter1).ToArray();// Arbitrarily return ItemsChoiceType.Parameter1
            }
            set
            {
                // Do nothing - don't care.
            }
        }
    }
    

XmlSerializer requires you to use one of these two options. If it cannot determine a correct element name by type or by item choice identifier, it will throw an InvalidOperationException with the message:

You need to add XmlChoiceIdentifierAttribute to the 'Parameters' member.

Prototype fiddle showing each option.

like image 176
dbc Avatar answered Oct 25 '22 06:10

dbc