Search code examples
xmlvb.netxml-deserialization

Order of data read from XML


I have to read data from an XML file and the order is important

I have the following (simplified) XML data which I need to read:

<xmltest>
    <type1>
        <tekst>ABC</tekst>
    </type1>
    <type2>
        <tekst>DEF</tekst>
    </type2>
    <type1>
        <tekst>GHI</tekst>
    </type1>
    <type2>
        <tekst>JKL</tekst>
    </type2>
    <type3>
        <tekst>MNO</tekst>
    </type3>
</xmltest>

The following classes are made:

public class xmltest
    public property type1 as list(of type1)
    public property type2 as list(of type2)
    public property type3 as list(of type3)
end class

public class type1
    public property tekst as string
end class

public class type2
    public property tekst as string
end class

public class type3
    public property tekst as string
end class

I use the following code to read the XML:

Public Sub Indlaes
    Dim reader As New System.Xml.XmlTextReader("filename.txt")
    Dim subreader As System.Xml.XmlReader
    Dim xmlSer As System.Xml.Serialization.XmlSerializer
    Dim result = New cmltest
   
    Do While (reader.Read())
        Select Case reader.NodeType
            Case System.Xml.XmlNodeType.Element
                subreader = reader.ReadSubtree()
                xmlSer = New System.Xml.Serialization.XmlSerializer(result.GetType)
                result = xmlSer.Deserialize(subreader)
                subreader.Close()
        End Select
    Loop
    reader.Close()
End Sub

In the above example I end up with 3 lists inside the xmltest but can't recreate the order
I'm thinking about using a dictionary with an ID used across the 3 lists/dictionaries, but how do I get the ID's set? Or is there any other solution?
I would really want to keep using the Deserialize function as it is used for the rest of the xml data also


Solution

  • The schema for your sequence of <typeN> elements looks to be a sequence of choice elements for each of possible <typeN> element name. As explained in Choice Element Binding Support, one way to implement this is via a list of polymorphic types, provided you know all all possible <typeN> element names at compile time, and apply the attributes <XmlElementAttribute(String, Type)> to the list for each possible type.

    The following classes implement this approach:

    ' The root object with the polymorphic list corresponding to the possible types 
    <XmlRoot("xmltest")> 
    Public Class XmlTest
        <XmlElement("type1", GetType(Type1)), XmlElement("type2", GetType(Type2)), XmlElement("type3", GetType(Type3))>     
        Public Property TypeList As List(Of TypeBase) = New List(Of TypeBase) ()
    End Class
    
    ' The <TypeN> polymorphic type hierarchy
    Public Class TypeBase 
        <XmlElement("tekst")>
        Public Property Tekst As String
    End Class
    
    <XmlType("type1")>
    Public Class Type1 
        Inherits TypeBase
    End Class
    
    <XmlType("type2")>
    Public Class Type2
        Inherits TypeBase
    End Class
    
    <XmlType("type3")>
    Public Class Type3 
        Inherits TypeBase
    End Class
    

    With these classes, you can deserialize automatically using XmlSerializer as follows, without any need for manual reading with XmlReader, using the following generic function:

    Public Function DeserializeFromFile(Of T)(filename As String) As T
        Using stream = File.OpenRead(filename)
            Return DirectCast(New XmlSerializer(GetType(T)).Deserialize(stream), T)
        End Using
    End Function
    

    Putting it all together, to deserialize your XML file, loop through the <typeN> items in their original order, and determine the type of each, you could do the following:

    Dim test = DeserializeFromFile(Of XmlTest)(filename)
    
    For Each item in test.TypeList
        Select Case item.GetType()
            Case GetType(Type1)
                Console.WriteLine("Type is type 1, value is {0}.", item.Tekst)
            Case GetType(Type2)
                Console.WriteLine("Type is type 3, value is {0}.", item.Tekst)
            Case GetType(Type3)
                Console.WriteLine("Type is type 3, value is {0}.", item.Tekst)
            Case Else
                Throw New Exception(String.Format("Unknown type {0}", item.GetType()))
        End Select
    Next
    

    Notes:

    Demo fiddle here.