问题
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
回答1:
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 byXmlSerializer
.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, thenxsd.exe
and Paste XML as Classes will not generate a correct type hierarchy using whateverxsi: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 namespacexmlns="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.
来源:https://stackoverflow.com/questions/50484173/deserialize-xml-with-multiple-types