I have an XML file similar to the following that was serialized from a set of models and XML annotations over properties.
<?xml version="1.0"?>
<RootElement xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" SomeAttribute="123">
<ChildElementList>
<SampleElement xsi:type="SampleElementX" SampleElementSomeAttribute="456">
<AnotherSampleElementList/>
</SampleElement >
</ChildElementList>
</RootElement >
I try to deserialize the existing XML file with a newly created POCO structure.
In order to be able to deserialize the file I need to match the element and attribute names match with my POCO objects however, changing the names of the POCO classes, and properties is not an option so I decided to rename the necessary elements and attributes during the read operation.
I can read and deserialize the RootElement's attributes without any problem.
While I read the XML file with the XML Reader, whenever I get to the "ChildElementList", the code goes into Skip. However, I also want to read and deserialize each element in that list. Why is that happening and how can I solve that problem? I really would appreciate some help.
I have a structure similar to following
XML Reader Override
Public Class CustomTextReader
Inherits XmlTextReader
Protected ReadOnly provider As XmlReadWriteProvider
Public Sub New(input As Stream)
MyBase.New(input)
End Sub
Public Sub New(provider As XmlReadWriteProvider, input As Stream)
MyBase.New(input)
Me.provider = provider
End Sub
Public Overrides Sub Skip()
MyBase.Skip()
End Sub
Public Overrides ReadOnly Property LocalName As String
Get
Return GetElementName(MyBase.LocalName)
End Get
End Property
Public Overrides ReadOnly Property Name As String
Get
Return GetElementName(MyBase.Name)
End Get
End Property
Public Overrides Function GetAttribute(localName As String, namespaceURI As String) As String
Dim att As String = MyBase.GetAttribute(localName, namespaceURI)
Return If(att IsNot Nothing, If(att = att.EndsWith("X"), att.Replace(att.Substring(-1, 1), "POCO"), att + "POCO"), att)
End Function
Function IsExcludedNode(nodeName As String) As Boolean
Return excludeNodes.Contains(nodeName)
End Function
Function GetElementName(elementName As String) As String
If MyBase.NodeType <> XmlNodeType.Element Then Return elementName
If IsExcludedNode(elementName) Then Return elementName
Return elementName + "POCO"
End Function
End Class
Serializing
Public Shared Function Deserialize(Of T)(fileName As String, extraTypes As Type()) As T
Using xmlFileStream As FileStream = New FileStream(fileName, FileMode.Open)
Using customXmlReader As XmlReader = XmlReadWriteProvider.GetXmlReader(GetType(T), xmlFileStream)
Dim xmlSerializer As XmlSerializer = GetXmlSerializer(GetType(T), GetXmlAttributeOverrides(extraTypes), extraTypes, Nothing, Nothing)
Deserialize = CType(xmlSerializer.Deserialize(customXmlReader), T)
End Using
End Using
Return Deserialize
End Function
Shared Function GetXmlSerializer(type As Type, [overrides] As XmlAttributeOverrides, extraTypes() As Type, root As XmlRootAttribute, defaultNamespace As String) As XmlSerializer
Dim customXmlSerializer As XmlSerializer = Nothing
customXmlSerializer = New XmlSerializer(type, [overrides], extraTypes, root, defaultNamespace)
Return customXmlSerializer
End Function
'Add XML attribute tag to the properties of all known extra types.
Private Shared Function GetXmlAttributeOverrides(extraTypes As Type()) As XmlAttributeOverrides
Dim xmlAttributeOverrides = New XmlAttributeOverrides
For Each type In extraTypes
AddAttributeOverrides(type, xmlAttributeOverrides)
Next
Return xmlAttributeOverrides
End Function
Private Shared Sub AddAttributeOverrides(type As Type, xmlAttributeOverrides As XmlAttributeOverrides)
For Each propertyInfo In type.GetProperties
xmlAttributeOverrides.Add(type, propertyInfo.Name, GetXmlAttribute(propertyInfo))
Next
End Sub
'If object is a primitive type it's an attribute, if it's a list it's an element
Shared Function GetXmlAttribute(propertyInfo As PropertyInfo) As XmlAttributes
If propertyInfo.IsPrimitive() Then
Return New XmlAttributes With {.XmlAttribute = New XmlAttributeAttribute With {.AttributeName = propertyInfo.Name}}
End If
If GetType(IEnumerable).IsAssignableFrom(propertyInfo.PropertyType) Then
Return New XmlAttributes With {.XmlArray = New XmlArrayAttribute With {.ElementName = propertyInfo.Name}}
End If
Return New XmlAttributes
End Function
<System.Runtime.CompilerServices.Extension>
Private Function IsPrimitiveInternal(t As Type) As Boolean
Return t.IsPrimitive OrElse t.IsValueType OrElse (t = GetType(String))
End Function
<System.Runtime.CompilerServices.Extension>
Function IsPrimitive(p As MemberInfo) As Boolean
Return IsPrimitiveInternal(If(TryCast(p, PropertyInfo)?.PropertyType, p))
End Function
<System.Runtime.CompilerServices.Extension>
Public Function GetListItemType(IEnumerableType As Type) As Type
If IEnumerableType.IsGenericType AndAlso IEnumerableType.GetGenericTypeDefinition() Is GetType(List(Of)) Then
Return IEnumerableType.GetGenericArguments(0)
End If
Return Nothing
End Function
Update: I realized that it works fine when the element names are not modified.
Considering my POCO class name is "SampleObjectPOCO", please see the following line
Return elementName + "POCO"
below
Function GetElementName(elementName As String) As String
If MyBase.NodeType <> XmlNodeType.Element Then Return elementName
If IsExcludedNode(elementName) Then Return elementName
Return elementName + "POCO" 'Not Working when the XML property name is "SampleElement"
End Function
If my XML file has the element name "SampleElementPOCO" instead of "SampleElement" it works fine. When I change it dynamically in the above line, it ceases working.
Function GetElementName(elementName As String) As String
If MyBase.NodeType <> XmlNodeType.Element Then Return elementName
If IsExcludedNode(elementName) Then Return elementName
Return elementName 'Working when the XML property name is "SampleElementPOCO"
End Function
In the end, I found out that XmlTextReader doesn't support changing the name of the XML element.
I used Xml.Linq to read the XML document into an XDocument and performed the following
Public Shared Function XMLTransformer(filepath As String) As XDocument
Dim doc = XDocument.Load(filepath)
Dim xsiNameSpace As XNamespace = "http://www.w3.org/2001/XMLSchema-instance"
For Each element As XElement In doc.Descendants
Dim typeAttribute As XAttribute = element.Attribute(xsiNameSpace + "type")
If typeAttribute IsNot Nothing Then
typeAttribute.Value = If(typeAttribute.Value.EndsWith("X"), typeAttribute.Value.Replace(typeAttribute.Value.Substring(typeAttribute.Value.Length - 1, 1), "POCO"), typeAttribute.Value + "POCO")
End If
If element.NodeType = XmlNodeType.Element AndAlso element.Attributes.Count > 0 Then
element.Name = element.Name.ToString + "POCO"
End If
Next
End Function
And then I give it to the Serializer as follows
SerializationManager.Deserialize(Of T)(SerializationHelper.GetExtraTypes,SerializationHelper.XMLTransformer(fileName))
I also created a Deserialize overload as follows
Public Shared Function Deserialize(Of T)(extraTypes As Type(), xmlContent As XDocument) As T
Dim xmlSerializer As XmlSerializer = New XmlSerializer(GetType(T), GetXmlAttributeOverrides(extraTypes), extraTypes, Nothing, Nothing)
Return CType(xmlSerializer.Deserialize(xmlContent.Root.CreateReader), T)
End Function