Serializing object graphs

Serialization is the process of converting a live object into a sequence of bytes using a specified format. Deserialization is the reverse process.

There are dozens of formats you can choose, but the two most common ones are eXtensible Markup Language (XML) and JavaScript Object Notation (JSON).

Tip

Good Practice

JSON is more compact and is best for web and mobile applications. XML is more verbose, but is better supported in older systems. Ironically, Microsoft originally chose JSON for the project.json file format in .NET Core 1.0, but then changed their mind and went back to XML with the C# project .csproj file format!

.NET Core has multiple classes that will serialize to and from XML and JSON. We will start by looking at XmlSerializer and JsonSerializer.

Serializing with XML

Add a new console application project named Ch10_Serialization.

In Visual Studio 2017, in the Solution Explorer, right-click Dependencies and choose Manage NuGet Packages. Search for System.Xml.XmlSerializer, select the found item, and then click on Install, as shown in the following screenshot:

Serializing with XML

Tip

Good Practice

If there are updates to other packages found, then install those too.

In Visual Studio Code, edit the Ch10_Serialization.csproj file to add a package reference for System.Xml.XmlSerializer version 4.3.0, as highlighted in the following markup:

    <Project Sdk="Microsoft.NET.Sdk"> 
 
      <PropertyGroup> 
        <OutputType>Exe</OutputType> 
        <TargetFramework>netcoreapp1.1</TargetFramework> 
      </PropertyGroup> 
 
      <ItemGroup> 
        <PackageReference Include="System.Xml.XmlSerializer"  
                          Version="4.3.0" /> 
      </ItemGroup> 
 
    </Project> 

To show a common example, we will define a custom class to store information about a person and then create an object graph using a list of Person instances with nesting.

Add a class named Person with the following definition. Notice that the Salary property is protected, meaning it is only accessible to itself and derived classes. To populate the salary, the class has a constructor with a single parameter to set the initial salary:

    using System; 
    using System.Collections.Generic; 
 
    namespace Ch10_Serialization 
    { 
      public class Person 
      { 
        public Person(decimal initialSalary) 
        { 
          Salary = initialSalary; 
        } 
        public string FirstName { get; set; } 
        public string LastName { get; set; } 
        public DateTime DateOfBirth { get; set; } 
        public HashSet<Person> Children { get; set; } 
        protected decimal Salary { get; set; } 
      } 
    } 

Back in Program.cs, import the following namespaces:

    using System; 
    using System.Collections.Generic; 
    using System.Xml.Serialization; 
    using System.IO; 
    using static System.Console; 

Add the following statements to the Main method:

    // create an object graph 
    var people = new List<Person> 
    { 
      new Person(30000M) { FirstName = "Alice", LastName = "Smith",  
        DateOfBirth = new DateTime(1974, 3, 14) }, 
      new Person(40000M) { FirstName = "Bob", LastName = "Jones",  
        DateOfBirth = new DateTime(1969, 11, 23) }, 
      new Person(20000M) { FirstName = "Charlie", LastName = "Rose",  
        DateOfBirth = new DateTime(1964, 5, 4),  
        Children = new HashSet<Person> 
        { new Person(0M) { FirstName = "Sally", LastName = "Rose",  
        DateOfBirth = new DateTime(1990, 7, 12) } } } 
    }; 
 
    // create a file to write to 
    string xmlFilepath = @"/Users/markjprice/Code/Ch10_People.xml"; 
    // string xmlFilepath = @"C:CodeCh10_People.xml"; // Windows 
    FileStream xmlStream = File.Create(xmlFilepath); 
 
    // create an object that will format as List of Persons as XML 
    var xs = new XmlSerializer(typeof(List<Person>)); 
 
    // serialize the object graph to the stream 
    xs.Serialize(xmlStream, people); 
 
    // you must close the stream to release the file lock 
    xmlStream.Dispose(); 
 
    WriteLine($"Written {new FileInfo(xmlFilepath).Length} bytes of
    XML to {xmlFilepath}"); 
    WriteLine(); 
 
    // Display the serialized object graph 
    WriteLine(File.ReadAllText(xmlFilepath)); 

Run the console application and view the output.

Note that an exception is thrown:

Unhandled Exception: System.InvalidOperationException:
Ch10_Serialization.Person cannot be serialized because it does not
have a parameterless constructor.

Back in the Person.cs file, add the following statement to define a parameter-less constructor. Note that the constructor does not need to do anything, but it must exist so that the XmlSerializer can call it to instantiate new Person instances during the deserialization process:

    public Person() { } 

Rerun the console application and view the output.

Note that the object graph is serialized as XML and the Salary property is not included:

Written 778 bytes of XML to C:CodeCh10_People.xml
<?xml version="1.0"?>
<ArrayOfPerson xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Person>
    <FirstName>Alice</FirstName>
    <LastName>Smith</LastName>
    <DateOfBirth>1974-03-14T00:00:00</DateOfBirth>
  </Person>
  <Person>
    <FirstName>Bob</FirstName>
    <LastName>Jones</LastName>
    <DateOfBirth>1969-11-23T00:00:00</DateOfBirth>
  </Person>
  <Person>
    <FirstName>Charlie</FirstName>
    <LastName>Rose</LastName>
    <DateOfBirth>1964-05-04T00:00:00</DateOfBirth>
    <Children>
      <Person>
        <FirstName>Sally</FirstName>
        <LastName>Rose</LastName>
        <DateOfBirth>1990-07-12T00:00:00</DateOfBirth>
      </Person>
    </Children>
  </Person>
</ArrayOfPerson>

We could make the XML more efficient using attributes instead of elements for some fields.

In the Person.cs file, import the System.Xml.Serialization namespace and modify all the properties, except Children, with the [XmlAttribute] attribute:

    [XmlAttribute("fname")] 
    public string FirstName { get; set; } 
    [XmlAttribute("lname")] 
    public string LastName { get; set; } 
    [XmlAttribute("dob")] 
    public DateTime DateOfBirth { get; set; } 

Rerun the application and notice that the XML is now more efficient:

Written 473 bytes of XML to C:CodeCh10_People.xml
<?xml version="1.0"?>
<ArrayOfPerson xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:xsd="
http://www.w3.org/2001/XMLSchema">
  <Person fname="Alice" lname="Smith" dob="1974-03-14T00:00:00" />
  <Person fname="Bob" lname="Jones" dob="1969-11-23T00:00:00" />
  <Person fname="Charlie" lname="Rose" dob="1964-05-04T00:00:00">
    <Children>
      <Person fname="Sally" lname="Rose" dob="1990-07-12T00:00:00" />
    </Children>
  </Person>
</ArrayOfPerson>

Deserializing with XML

Add the following statements to the end of the Main method:

    FileStream xmlLoad = File.Open(xmlFilepath, FileMode.Open); 
    // deserialize and cast the object graph into a List of Person 
    var loadedPeople = (List<Person>)xs.Deserialize(xmlLoad); 
    foreach (var item in loadedPeople) 
    { 
      WriteLine($"{item.LastName} has {item.Children.Count}
      children."); 
    } 
    xmlLoad.Dispose(); 

Rerun the application and notice that the people are loaded successfully from the XML file:

Smith has 0 children.
Jones has 0 children.
Rose has 1 children.

Customizing the XML

There are many other attributes that can be used to control the XML generated. See the references at the end of this chapter for more information.

Tip

Good Practice

When using XmlSerializer, remember that only public fields and properties are included, and the type must have a parameter-less constructor. You can customize the output with attributes.

Serializing with JSON

In Visual Studio 2017, in the Solution Explorer, in the Ch10_Serialization project, right-click on Dependencies and choose Manage NuGet Packages. Search for Newtonsoft.Json, select the found item, and then click on Install.

In Visual Studio Code, edit the Ch10_Serialization.csproj file to add a package reference for the Newtonsoft.Json version 9.0.1, as shown in the following markup:

    <PackageReference Include="Newtonsoft.Json"  
                      Version="9.0.1" /> 

Import the following namespace at the top of the Program.cs file:

    using Newtonsoft.Json; 

Add the following statements to the end of the Main method:

    // create a file to write to 
    string jsonFilepath = @"/Users/markjprice/Code/Ch10_People.json"; 
    // string jsonFilepath = @"C:CodeCh10_People.json"; // Windows 
    StreamWriter jsonStream = File.CreateText(jsonFilepath); 
 
    // create an object that will format as JSON 
    var jss = new JsonSerializer(); 
 
    // serialize the object graph into a string 
    jss.Serialize(jsonStream, people); 
 
    // you must dispose the stream to release the file lock 
    jsonStream.Dispose(); 
 
    WriteLine(); 
    WriteLine($"Written {new FileInfo(jsonFilepath).Length} bytes of
    JSON to: {jsonFilepath}"); 
 
    // Display the serialized object graph 
    WriteLine(File.ReadAllText(jsonFilepath)); 

Rerun the application and notice that JSON requires less than half the number of bytes compared to XML with elements. It's even smaller than XML, which uses attributes:

Written 368 bytes of JSON to: C:CodeCh10_People.json
[{"FirstName":"Alice","LastName":"Smith","DateOfBirth":"/Date(132451
    200000)/",
    "Children":null},{"FirstName":"Bob","LastName":"Jones","DateOfBirth":
    "/Date(-
    3369600000)/","Children":null},{"FirstName":"Charlie","LastName":"Ro
    se","DateOfBirth":"/Date(-
    178678800000)/","Children":[{"FirstName":"Sally","LastName":"Rose","
    DateOfBirth":"/Date(647737200000)/","Children":null}]}]

Tip

Good Practice

Use JSON to minimize the size of serialized object graphs. JSON is also a good choice when sending object graphs to web applications and mobile applications because JSON is the native serialization format for JavaScript.

Serializing with other formats

There are many other formats available as NuGet packages that you can use for serialization. A commonly used pair are: DataContractSerializer (for XML) and DataContractJsonSerializer (for JSON), which are both in the System.Runtime.Serialization namespace.

The main serializer that is available for .NET Framework but was not ported to .NET Core are the System.Runtime.Serialization formatters, especially the BinaryFormatter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.148.117.212