I am facing an issue while deserializing/serializing XML into a C# object. The XML structure has mixed order of elements, and I am encountering problems with elements that have the same name but different purposes. Here's a simplified version of my C# class:
public class Uientry
{
[XmlElement(ElementName = "u")]
public string ID { get; set; }
[XmlElement(ElementName = "s")]
public List<string> Strings { get; set; }
[XmlElement(ElementName = "i")]
public List<string> Integer { get; set; }
[XmlElement(ElementName = "additional_data")]
public Additional_data Additional_data { get; set; }
}
The issue arises when the XML has elements with the same name but different purposes, and their order is not fixed. For example, the <i>
elements (representing integers) can appear before and after <s>
elements (representing strings), causing conflicts.
Important:I want that the order of the elements stays the same after Serializing
this is some of my xml code
<uientry>
<u>606917248</u><!-- ID (80 d2 2c 24) -->
<s>root</s><!-- title -->
<s>CampaignRoot</s><!-- title2 -->
<i>0</i><!-- x offset -->
<i>0</i><!-- y offset -->
<no /><!-- uientry flag 1 -->
<yes /><!-- uientry flag 2 -->
<byte>1</byte><!-- uientry flag 3 -->
<no /><!-- uientry flag 4 -->
<no /><!-- uientry flag 5 -->
<no /><!-- uientry flag 6 -->
<no /><!-- uientry flag 7 -->
<yes /><!-- uientry flag 8 -->
<no /><!-- uientry flag 9 -->
<no /><!-- uientry flag 10 -->
<no /><!-- uientry flag 11 -->
<no /><!-- uientry flag 12 -->
<yes />
<unicode></unicode><!-- tooltip text -->
<unicode></unicode><!-- tooltip id -->
<i>0</i><!-- 00:00:00:00 --><!-- docking? -->
<i>0</i><!-- 00:00:00:00 --><!-- docking x? -->
<i>0</i><!-- 00:00:00:00 --><!-- docking y? -->
<no />
<i>0</i><!-- default state id -->
<images count="1">
<image>
<u>606535040</u><!-- ID (80 fd 26 24) -->
<s></s><!-- path -->
<i>1280</i><!-- x size -->
<i>960</i><!-- y size -->
<no />
</image>
</images>
<i>0</i><!-- 00:00:00:00 --><!-- mask image? -->
<i>0</i>
<states count="0">
</states>
<children count="0">
</children>
<additional_data type="none" />
</uientry>
How can I modify my C# class or use attributes to handle such a scenario? Is there a way to instruct the XML deserializer to consider the purpose of the element and handle it accordingly?
Your XML has a schema that includes a sequence of choice element. A choice element indicates that one of a fixed set of elements -- <u>
, <i>
, <yes>
, <no>
, <unicode>
and so on -- 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 anXmlChoiceIdentifierAttribute
in addition, and adds extra logic for making the choice.
Thus, as explained in this answer to Xml Deserialization - Merging two elements into a single List<T> object, you can either model this as a list of polymorphic types, or a pair of arrays of choice types and values. Personally I think the first option is easier, so let's go with that.
First define some base type UientryItem
, then for every element that might appear in <uientry>
, create a subtype corresponding to that element, indicating the name via an XmlTypeAttribute.TypeName
attribute property:
public abstract class UientryItem;
[XmlType("u"), XmlRoot("u")]
public class UientryId : UientryItem
{
[XmlText] public string Value { get; set; }
}
[XmlType("s"), XmlRoot("s")]
public class UientryTitle : UientryItem
{
[XmlText] public string Value { get; set; }
}
[XmlType("no"), XmlRoot("no")]
public class UientryNo : UientryItem;
[XmlType("yes"), XmlRoot("yes")]
public class UientryYes : UientryItem;
[XmlType("byte"), XmlRoot("byte")]
public class UientryByte : UientryItem
{
[XmlText] public byte Value { get; set; }
}
[XmlType("unicode"), XmlRoot("unicode")]
public class UientryUnicode : UientryItem
{
[XmlText] public string Value { get; set; }
}
[XmlType("i"), XmlRoot("i")]
public class UientryInt : UientryItem
{
[XmlText] public int Value { get; set; }
}
[XmlType("images"), XmlRoot("images")]
public class UientryImages : UientryItem
{
[XmlAttribute("count")]
public int Count { get => Images?.Count?? 0; set { } }
[XmlElement("image")]
public List<UientryImage> Images { get; set; } = new();
}
[XmlType("image"), XmlRoot("image")]
public class UientryImage
{
[XmlElement(typeof(UientryId)),
XmlElement(typeof(UientryTitle)),
XmlElement(typeof(UientryNo)),
XmlElement(typeof(UientryYes)),
XmlElement(typeof(UientryByte)),
XmlElement(typeof(UientryUnicode)),
XmlElement(typeof(UientryInt)),
XmlElement(typeof(UientryImages)),
XmlElement(typeof(UientryStates)),
XmlElement(typeof(UientryChildren)),
XmlElement(typeof(UientryAdditionalData))]
public List<UientryItem> Items { get; set; } = new();
}
[XmlType("states"), XmlRoot("states")]
public class UientryStates : UientryItem
{
[XmlAttribute("count")] public int Count { get; set; }
// TODO: fill in appropriate properties
}
[XmlType("children"), XmlRoot("children")]
public class UientryChildren : UientryItem
{
[XmlAttribute("count")] public int Count { get; set; }
// TODO: fill in appropriate properties
}
[XmlType("additional_data"), XmlRoot("additional_data")]
public class UientryAdditionalData : UientryItem
{
[XmlAttribute("type")] public string Type { get; set; }
// TODO: fill in appropriate properties
}
Next define Uientry
as follows:
[XmlType("uientry"), XmlRoot("uientry")]
public class Uientry
{
[XmlElement(typeof(UientryId)),
XmlElement(typeof(UientryTitle)),
XmlElement(typeof(UientryNo)),
XmlElement(typeof(UientryYes)),
XmlElement(typeof(UientryByte)),
XmlElement(typeof(UientryUnicode)),
XmlElement(typeof(UientryInt)),
XmlElement(typeof(UientryImages)),
XmlElement(typeof(UientryStates)),
XmlElement(typeof(UientryChildren)),
XmlElement(typeof(UientryAdditionalData))]
public List<UientryItem> Items { get; set; } = new();
}
And you will be able to deserialize your <uientry>
using a new XmlSerializer(typeof(Uientry))
e.g. as follows:
using var textReader = new StringReader(xmlString);
var serializer = new XmlSerializer(typeof(Uientry));
var uiEntry = (Uientry)serializer.Deserialize(textReader);
And the element order will be preserved via the order of items in the public List<UientryItem> Items { get; set; }
list.
Notes:
<uientry>
element -- for instance, that <additional_data>
always appears last -- then you can capture that with a separate property as you did in your original Uientry
class.Demo fiddle here.