6

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.

Natalie E
  • 75
  • 6
  • What do you want to do for serialization? – dbc May 18 '16 at 18:55
  • Also, do you need to know the element name with which a given element was serialized? – dbc May 18 '16 at 19:03
  • Either I don't understand the question or I don't understand how its related, but I'm deserializing the file to display options listed in it to a user. The end goal is just the text inside the elements, doesn't depend on the element names. (But of course different elements are for different options and what not). – Natalie E May 18 '16 at 19:16

2 Answers2

4

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.

dbc
  • 104,963
  • 20
  • 228
  • 340
0

What if you do something like this:

//get the xml doc
const string str = @"<root>
                       <Parameter1>3</Parameter1>
                       <Parameter2>4</Parameter2>
                     </root>";

var xml = new XmlDocument();

//load it
xml.LoadXml(str);

//get the nodes where the names contain the string parameter
var xnList = xml.SelectNodes("//*[contains(name(),'Parameter')]");

//create a list of parameters
var list = new List<Parameter>();

//populate the list with the value in the node's innertext
foreach (XmlNode xn in xnList)
{
    list.Add(new Parameter{ Value = int.Parse(xn.InnerText) } );
}      

foreach(var param in list)
    Console.WriteLine(param.Value); //should print 3 and 4

I am using this class as an example:

class Parameter{
    public int Value { get; set; }  
}
Luis Lavieri
  • 4,064
  • 6
  • 39
  • 69
  • The XML document i'm using is a lot bigger than what i pulled out for the example. Also, i'm using an XmlSerializer (not XmlDocument) so that won't work. – Natalie E May 18 '16 at 18:44