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).
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
.
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:
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>
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.
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.
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}]}]
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
.
3.148.117.212