xmlvb.netserializationxml-parsingxmlreader

XML Text Reader Override Skips Child Elements


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

Solution

  • 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