问题
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.
回答1:
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
XmlElementAttributeattributes to a public member. If they differ only by name, Xsd.exe applies anXmlChoiceIdentifierAttributein addition, and adds extra logic for making the choice.
Thus, you have the following options to deserialize your XML:
Subclass your
Parameterclass and specify different types for each element name, using [XmlElementAttribute(String, Type)]. The specificParametersubclass 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; } }If you want to use the same
Parametertype 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
ParametersElementNamearray will have the same number of entries as theParametersarray, and theenumvalues therein will indicate the XML element name actually encountered for each parameter.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.
回答2:
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; }
}
来源:https://stackoverflow.com/questions/37307203/xml-deserialization-merging-two-elements-into-a-single-listt-object