I'm trying to deserialize XML where some same name tags have different xsi types:
<user-defined-data-row>
<field name="entity">
<field-value xsi:type="field-text-valueType">
<value>Test</value>
</field-value>
</field>
<field name="expiry_date">
<field-value xsi:type="field-date-valueType">
<value>2001-10-07</value>
</field-value>
</field>
</user-defined-data-row>
This is easily achieved by deserializing the xml into this model:
[XmlRoot(ElementName = "field-value", Namespace = "http://www.crsoftwareinc.com/xml/ns/titanium/common/v1_0")]
[XmlType("field-text-valueType")]
public class Fieldvalue
{
[XmlElement(ElementName = "value", Namespace = "http://www.crsoftwareinc.com/xml/ns/titanium/common/v1_0")]
public string Value { get; set; }
}
The only thing that differs are the types in the XML:
field-text-valueType
field-date-valueType
How can I make the C# class interpret both types using something like
[XmlType("field-text-valueType")]
EDIT: deserializing not serializing
The most common usage of XML serialization is when XML data is sent from the database server to the client. Implicit serialization is the preferred method in most cases because it is simpler to code, and sending XML data to the client allows the Db2 client to handle the XML data properly.
XML serialization is the process of converting an object's public properties and fields to a serial format (in this case, XML) for storage or transport. Deserialization re-creates the object in its original state from the XML output.
The XmlSerializer enables you to control how objects are encoded into XML, it has a number of constructors. If you use any of the constructors other than the one that takes a type then a new temporary assembly is created EVERY TIME you create a serializer, rather than only once.
The xsi:type
attributes you are seeing in your XML are standard W3C XML Schema attributes that allow an element to explicitly specify its type; for details see here. As explained in Xsi:type Attribute Binding Support, XmlSerializer
supports this mechanism for deserialization of polymorphic types, specifically by use of XmlIncludeAttribute
.
First, define an abstract base class FieldValue
as follows:
public static class XmlNamespaces
{
public const string Crsoftwareinc = "http://www.crsoftwareinc.com/xml/ns/titanium/common/v1_0";
}
[XmlRoot("field-value", Namespace = XmlNamespaces.Crsoftwareinc)]
[XmlType("field-value", Namespace = XmlNamespaces.Crsoftwareinc)]
[XmlInclude(typeof(TextFieldValue)),
XmlInclude(typeof(DateFieldValue))]
public abstract partial class FieldValue
{
// It's not necessary to have this in the base class but I usually find it convenient.
public abstract object GetValue();
}
Next, for each possible xsi:type="XXX"
value, define a derived type of FieldValue
whose XmlTypeAttribute.TypeName
matches the xsi:type
value. Decorate the base class with [XmlInclude(typeof(TDerivedFieldValue))]
attributes for each (already shown above):
[XmlRoot("field-text-valueType", Namespace = XmlNamespaces.Crsoftwareinc)]
[XmlType("field-text-valueType", Namespace = XmlNamespaces.Crsoftwareinc)]
public class TextFieldValue : FieldValue
{
[XmlElement("value")]
public string Value { get; set; }
public override object GetValue() { return Value; }
}
[XmlRoot("field-date-valueType", Namespace = XmlNamespaces.Crsoftwareinc)]
[XmlType("field-date-valueType", Namespace = XmlNamespaces.Crsoftwareinc)]
public class DateFieldValue : FieldValue
{
[XmlElement("value", DataType = "date")]
public DateTime Value { get; set; }
public override object GetValue() { return Value; }
}
Then define the containing type corresponding to <field>
and other, higher elements as follows:
[XmlRoot("field", Namespace = XmlNamespaces.Crsoftwareinc)]
[XmlType("field", Namespace = XmlNamespaces.Crsoftwareinc)]
public class Field
{
[XmlAttribute("name")]
public string Name { get; set; }
[XmlElement("field-value")]
public FieldValue FieldValue { get; set; }
}
[XmlRoot("user-defined-data-row", Namespace = XmlNamespaces.Crsoftwareinc)]
[XmlType("user-defined-data-row", Namespace = XmlNamespaces.Crsoftwareinc)]
public class UserDefinedDataRow
{
[XmlElement("field")]
public List<Field> Fields { get; set; }
}
// The XML for the root object is not shown so this is just a stub
[XmlRoot("root", Namespace = XmlNamespaces.Crsoftwareinc)]
[XmlType("root", Namespace = XmlNamespaces.Crsoftwareinc)]
public class RootObject
{
[XmlElement("user-defined-data-row")]
public List<UserDefinedDataRow> Rows { get; set; }
}
Notes:
If the base class FieldValue
has a namespace specified in via XmlTypeAttribute.Namespace
, then the derived classes must also, or else an error will get thrown by XmlSerializer
.
Once an [XmlType]
namespace is defined, it automatically applies to all serialized properties, so it isn't necessary to specify the same namespace via [XmlElement(Namespace = "http://www.crsoftwareinc.com/xml/ns/titanium/common/v1_0")]
attributes.
I got tired of repeatedly typing the namespace "http://www.crsoftwareinc.com/xml/ns/titanium/common/v1_0"
and so I extracted it into a constant.
Other derived types of FieldType
can be added easily, e.g.:
[XmlRoot("field-decimal-valueType", Namespace = XmlNamespaces.Crsoftwareinc)]
[XmlType("field-decimal-valueType", Namespace = XmlNamespaces.Crsoftwareinc)]
public class DecimalFieldValue : FieldValue
{
[XmlElement("value")]
public decimal Value { get; set; }
public override object GetValue() { return Value; }
}
[XmlInclude(typeof(DecimalFieldValue))]
public abstract partial class FieldValue { }
Don't forget to add [XmlInclude(typeof(DecimalFieldValue))]
when doing so.
If you have been given an XSD for the XML you are trying to deserialize that specifies the possible types of <field-value>
via e.g. an <xsd:extension>
element as shown in Generating XML Documents from XML Schemas: Abstract Types, then xsd.exe
will generate classes that include an appropriate type hierarchy. But if you only have the XML, then xsd.exe
and Paste XML as Classes will not generate a correct type hierarchy using whatever xsi:type
attributes are present.
For more about this limitation see xsi:type attribute messing up C# XML deserialization.
Your XML is not well-formed because it omits a declaration for the xsi:
namespace. Also, a default namespace xmlns="http://www.crsoftwareinc.com/xml/ns/titanium/common/v1_0"
is not defined so none of the elements are actually in this namespace. Thus I assume your XML is a fragment of some larger document that is valid, e.g.
<root
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.crsoftwareinc.com/xml/ns/titanium/common/v1_0">
<user-defined-data-row>
<!-- Remainder as shown in the question -->
</user-defined-data-row>
</root>
Sample working .Net fiddle here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With