Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Complete Sample of Custom Class Collection Serialization and Deserialization

0.00/5 (No votes)
19 Jun 2014 1  
If you want to know the way you can serialize and deserialize custom class collection, this tip is for you.

Introduction

The most samples of serialization and deserialization available in net are written for C# and use List<T> general class.

This tip provides a way of serialization and deserialization of custom class which inherits from CollectionBase and explains the reason of the following issues:

  • There was an error reflecting type 'xxx'. (System.InvalidOperationException)
  • You must implement a default accessor on 'xxx' because it inherits from ICollection. (System.InvalidOperationException)

Background

I assume that you've got basic knowledge about:

  1. how to write custom class
  2. how to create collection of classes
  3. how to use nested classes
  4. what serialization and deserialization does/means

In case you want to round out your knowledge, here is a collection of useful links:

  1. MSDN
  2. CodeProject articles/tips

Case Study

Let' say you need to write application which:

  1. can collect notes and each note uses custom id
  2. provides a way to export data to XML file
  3. provides a way to import data from XML file

There is another requirement: XML file must fulfil schema requirements (*.xsd file):

<!--?xml version="1.0"?-->
<xs:schema targetnamespace="urn:SoftwareDeveloperName" 
xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:annotation>
    <xs:documentation>
      -- Copyright (C) 2014 (author: Your Name here). --
    </xs:documentation>
  </xs:annotation>
  <xs:element name="MyData">
    <xs:complextype>
      <xs:sequence>
        <xs:element type="xs:string" name="Version" fixed="1.0">
        <xs:element name="Notes" minoccurs="1" maxoccurs="unbounded">
          <xs:complextype>
            <xs:sequence>
              <xs:element name="Note" minoccurs="1" maxoccurs="unbounded">
                <xs:complextype>
                  <xs:simplecontent>
                    <xs:extension base="xs:string">
                      <xs:attribute type="xs:integer" name="ID">
                      </xs:attribute>
                    </xs:extension>
                  </xs:simplecontent>
                </xs:complextype>
              </xs:element><!-- EOF Note -->
            </xs:sequence>
          </xs:complextype>
        </xs:element><!-- EOF Notes-->
      </xs:element></xs:sequence>
    </xs:complextype>
  </xs:element><!-- EOF MyData -->

Sample data:

<!--?xml version="1.0"?-->
<mydata>
  <version>1.0</version>
  <notes>
    <note id="1">Some text</note>
    <note id="2">Another text</note>
    <note id="3">Hello kitty!</note>
    <note id="4">Good bye, my love...</note>
  </notes>

Till now the task looks trivial, true?

The Mystery of Success

The mystery of success is to properly define MyData class.

Have a look at the below diagram:

As you can see, MyData class has 2 fields:

  • Version - string property to store version information
  • Notes - nested class, the collection of Note class

There is a few other things to remember:

  • Serialization and deserialization of object (class) via XMLSerializer is possible on publicly visible and read-write fields!
  • You can ignore some fields or change its destination name by using attributes
  • All publicly visible types cannot have parameters
  • The XmlSerializer cannot serialize or deserialize: arrays of ArrayList or arrays of List(Of T)
  • It's possible to serialize/deserialize collection even if its property is set as ReadOnly.
  • The collection must have default accessor

Definition of Classes

Note class:

<serializable()> _
Public Class Note
    Private iId As Integer = 0
    Private sText As String = String.Empty

    <XmlAttribute("ID")> _
    Public Property ID() As Integer
        Get
            Return iId
        End Get
        Set(value As Integer)
            iId = value
        End Set
    End Property

    <XmlText()> _
    Public Property Text() As String
        Get
            Return sText
        End Get
        Set(value As String)
            sText = value
        End Set
    End Property

End Class

The definition of Note class is very simple. Its XML representation must be:

<note id="1">Some Text</note>

instead of:

<note><id>1</id><text>Some Text</text></note>

or:

<note id="1"><text>Some Text</text></note>

That's why ID field has been defined as a XmlAttribute of XML element (Note) and Text has been defined as a XmlText:

NotesCollection class:

Partial Public Class MyData
    Public Class NotesCollection
        Inherits CollectionBase

        Public Sub Add(ByVal oNote As Note)
            List.Add(oNote)
        End Sub

        Public Sub Remove(ByVal index As Integer)
            If index > Count - 1 Or index < 0 Then
                Console.WriteLine("Index not valid!")
            Else
                List.RemoveAt(index)
            End If
        End Sub

        'default accessor to the Note class
        Default Public ReadOnly Property Item(ByVal index As Integer) As Note
            Get
                Return CType(List.Item(index), Note)
            End Get
        End Property

    End Class
End Class

Have a look at the definition of Item property! It's default accessor to Note class!

And finally the definition of MyData class:

<serializable()> _
<XmlRoot("MyData")> _
Public Class MyData

    Private oNotes As NotesCollection = New NotesCollection
    Private sVersion As String = "1.0"

    <XmlElement("Version")> _
    Public Property Version As String
        Get
            Return sVersion
        End Get
        Set(value As String)
            sVersion = value
        End Set
    End Property

    <XmlArray("Notes"), XmlArrayItem("Note", GetType(Note))> _
    Public ReadOnly Property Notes As NotesCollection
        Get
            Return oNotes
        End Get
    End Property

End Class

The most important for serialization and deserialization of Notes property is this line:

<XmlArray("Notes"), XmlArrayItem("Note", GetType(Note))> _

It means: treat this property as an array of objects (XML elements).

Usage

Module Module1

    Sub Main()
        'consts:
        Const sFile As String = "D:\Notes.xml"
        'vars:
        Dim oData As MyData = Nothing
        Dim oNote As Note = Nothing
        Dim ans As ConsoleKeyInfo = Nothing

        'create instance of MyData 
        oData = New MyData
        'add notes
        For i As Integer = 1 To 26
            oNote = New Note
            With oNote
                .ID = i
                .Text = Chr(64 + i)
            End With
            oData.Notes.Add(oNote)
        Next
        'serialize data
        Console.WriteLine("Starting serialization...")
        SerializeMyData(oData, sFile)
        Console.WriteLine("Do you want to see XML file (y|n)?")
        ans = Console.ReadKey(True)
        If ans.KeyChar = "y" Then Process.Start(sFile)

        'deserialize data
        oData = Nothing 'clean up!
        oData = New MyData() 'create new instance of MyData class
        Console.WriteLine("Starting deserialization...")
        Console.WriteLine("")
        oData = DeserializeMyData(oData, sFile)
        Console.WriteLine("Ready!")
        'display
        Console.WriteLine("")
        Console.WriteLine("Version: {0}", oData.Version)
        Console.WriteLine("Notes:")
        For Each n As Note In oData.Notes
            Console.WriteLine("ID: {0}; Text: {1}", n.ID, n.Text)
        Next
        Console.WriteLine("")
        Console.WriteLine("Done!")
        Console.WriteLine("Press any key to exit.")
        Console.ReadKey()

    End Sub

    Private Sub SerializeMyData(ByVal obj As MyData, ByVal sFileName As String)
        Dim serializer As XmlSerializer = Nothing
        Dim fs As System.IO.FileStream = Nothing

        Try
            serializer = New XmlSerializer(obj.GetType())
            fs = New System.IO.FileStream(sFileName, IO.FileMode.Create)
            serializer.Serialize(fs, obj)
            fs.Close()

        Catch ex As Exception
            Console.WriteLine(ex.Message)

        Finally
            If Not fs Is Nothing Then fs.Close()
            fs = Nothing
            serializer = Nothing
        End Try
    End Sub

    Private Function DeserializeMyData(ByVal obj As MyData, ByVal sFileName As String) As MyData
        Dim serializer As XmlSerializer = Nothing
        Dim fs As System.IO.FileStream = Nothing

        Try
            serializer = New XmlSerializer(obj.GetType())
            fs = New System.IO.FileStream(sFileName, IO.FileMode.Open)
            obj = serializer.Deserialize(fs)
            fs.Close()

        Catch ex As Exception
            Console.WriteLine(ex.Message)

        Finally
            If Not fs Is Nothing Then fs.Close()
            fs = Nothing
            serializer = Nothing
        End Try

        Return obj

    End Function

End Module

Points of Interest

I hope that reading this tip saved your time against possible problems while writing your code.

Please ask if you have any questions.

History

  • 2014-06-19 - Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here