Deserializing XML into object

davearia

Well-known member
Joined
Jan 4, 2005
Messages
184
Hi,

I am now trying deserialize my employee objects in my XML document into an instance of a class.

Here is the class I wish to create an instance of:
Code:
Public Class EmployeeAdder

#Region "Declarations"
    Private _ID As Int32 = 0
    Private _Name As String = String.Empty
    Private _ActivityID As Int32 = 0
#End Region

#Region "Properties"
    Public Property ID() As Int32
        Get
            Return _ID
        End Get
        Set(ByVal value As Int32)
            _ID = value
        End Set
    End Property

    Public Property Name() As String
        Get
            Return _Name
        End Get
        Set(ByVal value As String)
            _Name = value
        End Set
    End Property

    Public Property ActivityID() As Int32
        Get
            Return _ActivityID
        End Get
        Set(ByVal value As Int32)
            _ActivityID = value
        End Set
    End Property
#End Region
End Class

Here is the XML document I am using:
Code:
<Employees xmlns="http://www.me.com">
  <Employee>
    <ID>11</ID>
    <Name>Dave Jones</Name>
    <ActivityID>9</ActivityID>
  </Employee>
  <Employee>
    <ID>111</ID>
    <Name>Wayne Wallice</Name>
    <ActivityID>1</ActivityID>
  </Employee>
  <Employee>
    <ID>1111</ID>
    <Name>Justin Davies</Name>
    <ActivityID>2</ActivityID>
  </Employee>
  <Employee>
    <ID>11111</ID>
    <Name>Matthew Jones</Name>
    <ActivityID>0</ActivityID>
  </Employee>
  <Employee>
    <ID>111111</ID>
    <Name>Steve Pear</Name>
    <ActivityID>2</ActivityID>
  </Employee>
</Employees>

Finally here is my function that I am trying to get to work:

Code:
    Private Function AddEmployee(ByVal xmlDoc As XmlDocument) As Boolean
        Get root node and process its children.
        Try
            Dim xmlNodeEmployees As XmlNode = xmlDoc.FirstChild
            For Each node As XmlNode In xmlNodeEmployees.ChildNodes
                Dim buffer() As Byte = System.Text.ASCIIEncoding.ASCII.GetBytes(node.InnerXml)
                Dim memstrm As New System.IO.MemoryStream(buffer)
                Dim xmlReader As New XmlTextReader(memstrm)
                Dim employeeAdder As New EmployeeAdder
                Dim serializer As New XmlSerializer(GetType(EmployeeAdder))
                employeeAdder = CType(serializer.Deserialize(xmlReader), EmployeeAdder)
            Next
        Catch ex As Exception
            Return False
        End Try
    End Function

When it runs it get as far as employeeAdder = CType(serializer.Deserialize(xmlReader), EmployeeAdder) and then throws this exception:
InnerException = {"<ID xmlns=http://www.me.com> was not expected."}

Does anyone know what I am doing wrong here?

Thanks, Dave.
 
Does anyone know what I am doing wrong here?
Youre trying to deserialize a list of objects into a single object. It seems list youre also using XML that you created, not XML created by the serialization process. Try:
Code:
Dim lst As New List(Of EmployeeAdder)
Dim ser As New Xml.Serialization.XmlSerializer(GetType(List(Of EmployeeAdder)))
Dim reader As New System.IO.StreamReader(New System.IO.FileStream(Application.StartupPath & "\test.xml", IO.FileMode.Open))

lst = CType(ser.Deserialize(reader), List(Of EmployeeAdder))
using XML formatted like this:
Code:
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfEmployeeAdder xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <EmployeeAdder>
    <ID>1</ID>
    <Name>Test</Name>
    <ActivityID>1</ActivityID>
  </EmployeeAdder>
  <EmployeeAdder>
    <ID>2</ID>
    <Name>Test 2</Name>
    <ActivityID>2</ActivityID>
  </EmployeeAdder>
</ArrayOfEmployeeAdder>
 
Hi,

Sorry Machaira I didnt get back to you sooner.

I am not trying to deserialize all of the objects at once. The code is getting all of the child nodes of the employees root node and creating an instance of each employee.

As this XML comes from a file already created, I didnt really want to use XML created by serialization. I assume this is possible?

I have updated the method, and have a temporary fix to it by using X-Path to interrigate the nodes. But it would be much nicer to deserialize straight from the XML document.

The issue seems to be with the namespace but I cannot see what it is that is wrong.

Please help if you can.

Many thanks, Dave.
 
If youre using an XML format that the serializer can use you cant use serialization. Since youve already go the XMLDocument object you should just be able to iterate through the nodes and populate the properties of an employee object. I think youre making it harder than it needs to be by introducing things like the stream and XmlTextReader. Try this:

Code:
	Dim lst As New List(Of EmployeeAdder)
		Dim emp As EmployeeAdder
		Dim doc As New Xml.XmlDocument
		Dim node As Xml.XmlNode

		doc.Load(Application.StartupPath & "\test.xml")

		For Each node In doc.DocumentElement

			emp = New EmployeeAdder

			emp.ActivityID = Convert.ToInt32(node.SelectSingleNode("descendant::ActivityID").InnerText)
			emp.ID = Convert.ToInt32(node.SelectSingleNode("descendant::ID").InnerText)
			emp.Name = node.SelectSingleNode("descendant::Name").InnerText

			lst.Add(emp)

		Next node
If you have to have the "xmlns="http://www.me.com", youll need to add the code to recognize it.
 
Hi,

I have already coded it to iterate through the document like you described and it works just fine. I would like to be able to manage the same task using deserialization instead for future projects etc.

I have Googled this quite a lot and it does seem like the class I am trying to create by deserializing the XML has to reference this namspace or else they do not match, giving this error.

Reading articles like http://www.microsoft.com/belux/msdn/nl/community/columns/jdruyts/wsproxy.mspx it appears in C# there is a way of adding attributes to classes and their properties to acheive this. However I cannot find a V.B. equivalent.

Looking the MSDN I found this: ms-help://MS.MSDNQTR.v80.en/MS.MSDN.v80/MS.NETDEVFX.v20.en/CPref19/html/C_System_Xml_Serialization_XmlTypeAttribute_ctor.htm

This lead me to this adoptation:

Code:
Private Sub AddEmployee(ByVal xmlDoc As XmlDocument, ByVal requestID As Int32)
        Dim nsm As New System.Xml.XmlNamespaceManager(xmlDoc.NameTable)
    
        nsm.AddNamespace("ns", Resources.Resource.AddEmployeeNameSpace)
        Get root node and process its children.
        Dim xmlNodeEmployees As XmlNode = xmlDoc.SelectSingleNode("ns:Employees", nsm)
        For Each node As XmlNode In xmlNodeEmployees.ChildNodes
            Dim buffer() As Byte = System.Text.ASCIIEncoding.ASCII.GetBytes(node.OuterXml)
            Dim memstrm As New System.IO.MemoryStream(buffer)
            Dim xmlReader As New XmlTextReader(memstrm)

            Dim employeeAdder As New EmployeeAdder

            Dim employeeOverride As New XmlAttributeOverrides()
            Dim employeeAttributes As New XmlAttributes()
            Dim employeeType As New XmlTypeAttribute()

            employeeType.TypeName = "employeeAdder"
            employeeType.Namespace = Resources.Resource.AddEmployeeNameSpace

            employeeAttributes.XmlType = employeeType

            employeeOverride.Add(GetType(EmployeeAdder), employeeAttributes)

            Dim serializer As New XmlSerializer(GetType(EmployeeAdder), employeeOverride)

            Try
                employeeAdder = CType(serializer.Deserialize(xmlReader), EmployeeAdder)
            Catch ex As Exception
                Dim str As String = ex.ToString
            Finally
                memstrm.Close()
            End Try

         Next
    End Sub

But I just get the same error as I got at the start of this post.

Can anyone help?

Thanks, Dave.
 
Under vb the same attributes should work, you just enclose them in angle brackets rather than square ones.
i.e. for the c#
C#:
[XmlAttribute("Test")]
public int Sample;

the vb would be
Code:
<XmlAttribute("Test")> _
Public Sample as Integer
 
Hi,

So where and how do I add these to the following class to give this class the namespace? I m only asking because I am starting to get lost.

I tried quite a few attempts with the angle brackets before and after the last post with no luck.

Any help would be appreciated!

Code:
#Region "Imports"
Imports System.Xml
#End Region

Public Class EmployeeAdder

#Region "Declarations"
    Private _KronosID As Int32 = 0
    Private _Name As String = String.Empty
    Private _ActivityID As Int32 = 0
#End Region

#Region "Properties"
    Public Property KronosID() As Int32
        Get
            Return _KronosID
        End Get
        Set(ByVal value As Int32)
            _KronosID = value
        End Set
    End Property

    Public Property Name() As String
        Get
            Return _Name
        End Get
        Set(ByVal value As String)
            _Name = value
        End Set
    End Property

    Public Property ActivityID() As Int32
        Get
            Return _ActivityID
        End Get
        Set(ByVal value As Int32)
            _ActivityID = value
        End Set
    End Property
#End Region

End Class
 
Code:
Public Class EmployeeAdder

    <XmlAttribute("KronosID")> _
    Public KronosID As Int32 = 0    
    <XmlAttribute("Name")> _
    Public Name As String = String.Empty    
    <XmlAttribute("ActivityID")> _
    Public ActivityID As Int32 = 0

End Class
Since youre not doing any validation in the property accessors just make the members public.

I still dont understand the reasoning behind making things more difficult than you need to.
 
Hi,

If I put the above code into my solution including import System.XML I get the error: XmlAttribute cannot be used as an attribute because it does not inherit from System.Attribute.

I appreciate you thinking I am making this harder than it needs be. But the reason for wanting to get this to work is so that in future I can deserialize xml with namspaces into objects etc.

Many thanks to everyone especially Machaira for their help so far.

Any ideas what this error is caused by?

Cheers, Dave.
 
You should be able to use the code I gave in post #4, you just need to take the namespace into account. Maybe Im just missing something about exactly why youre trying to do it the way youre doing it. :(
 
Hi Machaira,

Okay, let me explain why I want to acheive this the way I am trying to.

This code is part of a small web service I have wrote. The service currently serves 3 public functions that, adds an employee, updates an employee, deletes an employee. Each takes an xml document as a parameter.

Each of the calls firstly validates the xml document passed in. This is why the namespace is included in the xml, to tie the xml to to XSD stored on the server. If the xml does not validate the service exits, if it validates then the process continues.

Currently I am doing the process in the way you suggested in post #4 in that I am iterating through the nodes and getting values for name and ID etc to populate an instance of the class. This is fine and works well, but lets say there are not 3 possible classes we need to create and therefore have to iterate through the xml structure, but 30.

That means I would need to write 30 routines that iterate through each specific node structure. Although this is possible I think the following is a more desired fix.

We have a generic sub that gets passed the xml document and enum that tells the sub what the request type something like the following:

Code:
Private Sub AddEmployee(ByVal xmlDoc As XmlDocument, ByVal requestID As Int32)
        Dim nsm As New System.Xml.XmlNamespaceManager(xmlDoc.NameTable)
        Dim obj As New Object

        Select Case requestID
            Case requestType.insert
                obj = New EmployeeAdder
                nsm.AddNamespace("ns", Resources.Resource.AddEmployeeNameSpace)
            Case requestType.update
                obj = New EmployeeUpdater
                nsm.AddNamespace("ns", Resources.Resource.UpdateEmployeeNameSpace)
            Case requestType.delete
                obj = New EmployeeDeleter
                nsm.AddNamespace("ns", Resources.Resource.DeleteEmployeeNameSpace)
        End Select

        Get root node and process its children.
        Dim xmlNodeEmployees As XmlNode = xmlDoc.SelectSingleNode("ns:Employees", nsm)
        For Each node As XmlNode In xmlNodeEmployees.ChildNodes

            Deserialize somehow?

            Dim result As Int32 = _BusinessServices.AddEmployee(obj)
        Next
    End Sub

Once the code gets the correct namespace at the start of the sub we can also create our class as well, it would then be sensible/generic to use the current xml node to deserialize this to create instance of this class.

This approach to me seems a pain to get to work initially, but in the long run so much easier to maintain etc.

I hope that I have made more sense this time. I know once I get into something it is very easy to assume that everyone else knows what youre trying to do. :D

Thanks for all your help so far, Dave. :D
 
If there is a valid xsd for the xml file then you could try the xsd.exe command line tool.
xsd.exe /c /l:vb <path to the xsd> should work
 
Back
Top