Chapter 17 - Serialization
XmlSerializer
Attribute-based serialization - Getting started
void Main()
{
Person p = new Person();
p.Name = "Stacey"; p.Age = 30;
var xs = new XmlSerializer (typeof (Person));
using (Stream s = File.Create ("person.xml"))
xs.Serialize (s, p);
Person p2;
using (Stream s = File.OpenRead ("person.xml"))
p2 = (Person)xs.Deserialize (s);
Console.WriteLine (p2.Name + " " + p2.Age); // Stacey 30
File.ReadAllText ("person.xml").Dump ("XML");
}
public class Person
{
public string Name;
public int Age;
}
Attribute-based serialization - attributes names and namespaces
void Main()
{
Person p = new Person();
p.Name = "Stacey"; p.Age = 30;
var xs = new XmlSerializer (typeof (Person));
using (Stream s = File.Create ("person.xml"))
xs.Serialize (s, p);
Person p2;
using (Stream s = File.OpenRead ("person.xml"))
p2 = (Person)xs.Deserialize (s);
Console.WriteLine (p2.Name + " " + p2.Age); // Stacey 30
File.ReadAllText ("person.xml").Dump ("XML");
}
[XmlRoot ("Candidate", Namespace = "http://mynamespace/test/")]
public class Person
{
[XmlElement ("FirstName")] public string Name;
[XmlAttribute ("RoughAge")] public int Age;
}
Attribute-based serialization - XML element order
void Main()
{
Person p = new Person();
p.Name = "Stacey"; p.Age = 30;
var xs = new XmlSerializer (typeof (Person));
using (Stream s = File.Create ("person.xml"))
xs.Serialize (s, p);
Person p2;
using (Stream s = File.OpenRead ("person.xml"))
p2 = (Person)xs.Deserialize (s);
Console.WriteLine (p2.Name + " " + p2.Age); // Stacey 30
File.ReadAllText ("person.xml").Dump ("XML");
}
public class Person
{
[XmlElement (Order = 2)] public string Name;
[XmlElement (Order = 1)] public int Age;
}
Subclasses and Child Objects - subclassing root type
void Main()
{
var p = new Student { Name = "Stacey" };
SerializePerson (p, "person.xml");
File.ReadAllText ("person.xml").Dump ("XML");
}
public void SerializePerson (Person p, string path)
{
XmlSerializer xs = new XmlSerializer (typeof (Person));
using (Stream s = File.Create (path))
xs.Serialize (s, p);
}
[XmlInclude (typeof (Student))]
[XmlInclude (typeof (Teacher))]
public class Person { public string Name; }
public class Student : Person { }
public class Teacher : Person { }
Subclasses and Child Objects - serializing child objects
void Main()
{
Person p = new Person { Name = "Stacey" };
p.HomeAddress.Street = "Odo St";
p.HomeAddress.PostCode = "6020";
SerializePerson (p, "person.xml");
File.ReadAllText ("person.xml").Dump ("XML");
}
public void SerializePerson (Person p, string path)
{
XmlSerializer xs = new XmlSerializer (typeof (Person));
using (Stream s = File.Create (path))
xs.Serialize (s, p);
}
public class Person
{
public string Name;
public Address HomeAddress = new Address();
}
public class Address { public string Street, PostCode; }
Subclasses and Child Objects - subclassing child objects - option 1
void Main()
{
Person p = new Person { Name = "Stacey" };
p.HomeAddress.Street = "Odo St";
p.HomeAddress.PostCode = "6020";
SerializePerson (p, "person.xml");
File.ReadAllText ("person.xml").Dump ("XML");
}
public void SerializePerson (Person p, string path)
{
XmlSerializer xs = new XmlSerializer (typeof (Person));
using (Stream s = File.Create (path))
xs.Serialize (s, p);
}
[XmlInclude (typeof (AUAddress))]
[XmlInclude (typeof (USAddress))]
public class Address { public string Street, PostCode; }
public class USAddress : Address { }
public class AUAddress : Address { }
public class Person
{
public string Name;
public Address HomeAddress = new USAddress();
}
Subclasses and Child Objects - subclassing child objects - option 2
void Main()
{
Person p = new Person { Name = "Stacey" };
p.HomeAddress.Street = "Odo St";
p.HomeAddress.PostCode = "6020";
SerializePerson (p, "person.xml");
File.ReadAllText ("person.xml").Dump ("XML");
}
public void SerializePerson (Person p, string path)
{
XmlSerializer xs = new XmlSerializer (typeof (Person));
using (Stream s = File.Create (path))
xs.Serialize (s, p);
}
public class Address { public string Street, PostCode; }
public class USAddress : Address { }
public class AUAddress : Address { }
public class Person
{
public string Name;
[XmlElement ("Address", typeof (Address))]
[XmlElement ("AUAddress", typeof (AUAddress))]
[XmlElement ("USAddress", typeof (USAddress))]
public Address HomeAddress = new USAddress();
}
Serializing Collections
void Main()
{
Person p = new Person { Name = "Stacey" };
p.Addresses.Add (new Address { Street = "My Street", PostCode = "1234" });
p.Addresses.Add (new Address { Street = "My Way", PostCode = "2345" });
SerializePerson (p, "person.xml");
File.ReadAllText ("person.xml").Dump ("XML");
}
public void SerializePerson (Person p, string path)
{
XmlSerializer xs = new XmlSerializer (typeof (Person));
using (Stream s = File.Create (path))
xs.Serialize (s, p);
}
public class Person
{
public string Name;
public List<Address> Addresses = new List<Address>();
}
public class Address { public string Street, PostCode; }
Serializing Collections - renaming elements
void Main()
{
Person p = new Person { Name = "Stacey" };
p.Addresses.Add (new Address { Street = "My Street", PostCode = "1234" });
p.Addresses.Add (new Address { Street = "My Way", PostCode = "2345" });
SerializePerson (p, "person.xml");
File.ReadAllText ("person.xml").Dump ("XML");
}
public void SerializePerson (Person p, string path)
{
XmlSerializer xs = new XmlSerializer (typeof (Person));
using (Stream s = File.Create (path))
xs.Serialize (s, p);
}
public class Person
{
public string Name;
[XmlArray ("PreviousAddresses")]
[XmlArrayItem ("Location")]
public List<Address> Addresses = new List<Address>();
}
public class Address { public string Street, PostCode; }
Serializing Collections - without outer element
void Main()
{
Person p = new Person { Name = "Stacey" };
p.Addresses.Add (new Address { Street = "My Street", PostCode = "1234" });
p.Addresses.Add (new Address { Street = "My Way", PostCode = "2345" });
SerializePerson (p, "person.xml");
File.ReadAllText ("person.xml").Dump ("XML");
}
public void SerializePerson (Person p, string path)
{
XmlSerializer xs = new XmlSerializer (typeof (Person));
using (Stream s = File.Create (path))
xs.Serialize (s, p);
}
public class Person
{
public string Name;
[XmlElement ("Address")]
public List<Address> Addresses = new List<Address>();
}
public class Address { public string Street, PostCode; }
Serializing Collections - subclassed elements with type attribute
void Main()
{
Person p = new Person { Name = "Stacey" };
p.Addresses.Add (new USAddress { Street = "My Street", PostCode = "90210" });
p.Addresses.Add (new AUAddress { Street = "My Way", PostCode = "6000" });
SerializePerson (p, "person.xml");
File.ReadAllText ("person.xml").Dump ("XML");
}
public void SerializePerson (Person p, string path)
{
XmlSerializer xs = new XmlSerializer (typeof (Person));
using (Stream s = File.Create (path))
xs.Serialize (s, p);
}
[XmlInclude (typeof (AUAddress))]
[XmlInclude (typeof (USAddress))]
public class Address { public string Street, PostCode; }
public class USAddress : Address { }
public class AUAddress : Address { }
public class Person
{
public string Name;
[XmlElement ("Address")]
public List<Address> Addresses = new List<Address>();
}
Serializing Collections - subclassed elements with name
void Main()
{
Person p = new Person { Name = "Stacey" };
p.Addresses.Add (new USAddress { Street = "My Street", PostCode = "90210" });
p.Addresses.Add (new AUAddress { Street = "My Way", PostCode = "6000" });
SerializePerson (p, "person.xml");
File.ReadAllText ("person.xml").Dump ("XML");
}
public void SerializePerson (Person p, string path)
{
XmlSerializer xs = new XmlSerializer (typeof (Person));
using (Stream s = File.Create (path))
xs.Serialize (s, p);
}
[XmlInclude (typeof (AUAddress))]
[XmlInclude (typeof (USAddress))]
public class Address { public string Street, PostCode; }
public class USAddress : Address { }
public class AUAddress : Address { }
public class Person
{
public string Name;
[XmlElement ("Address")]
public List<Address> Addresses = new List<Address>();
}
Serializing Collections - subclassed elements with name (no outer element)
void Main()
{
Person p = new Person { Name = "Stacey" };
p.Addresses.Add (new USAddress { Street = "My Street", PostCode = "90210" });
p.Addresses.Add (new AUAddress { Street = "My Way", PostCode = "6000" });
SerializePerson (p, "person.xml");
File.ReadAllText ("person.xml").Dump ("XML");
}
public void SerializePerson (Person p, string path)
{
XmlSerializer xs = new XmlSerializer (typeof (Person));
using (Stream s = File.Create (path))
xs.Serialize (s, p);
}
public class Address { public string Street, PostCode; }
public class USAddress : Address { }
public class AUAddress : Address { }
public class Person
{
public string Name;
[XmlElement ("Address", typeof (Address))]
[XmlElement ("AUAddress", typeof (AUAddress))]
[XmlElement ("USAddress", typeof (USAddress))]
public List<Address> Addresses = new List<Address>();
}
Interoperating with IXmlSerializable
void Main()
{
Person p = new Person
{
Name = "Stacey",
HomeAddress = new Address { Street = "My Street", PostCode = "90210" }
};
SerializePerson (p, "person.xml");
File.ReadAllText ("person.xml").Dump ("XML");
}
public void SerializePerson (Person p, string path)
{
XmlSerializer xs = new XmlSerializer (typeof (Person));
using (Stream s = File.Create (path))
xs.Serialize (s, p);
}
public class Person
{
public string Name;
public Address HomeAddress;
}
public class Address : IXmlSerializable
{
public string Street, PostCode;
public XmlSchema GetSchema() { return null; }
public void ReadXml (XmlReader reader)
{
reader.ReadStartElement();
Street = reader.ReadElementContentAsString ("Street", "");
PostCode = reader.ReadElementContentAsString ("PostCode", "");
reader.ReadEndElement();
}
public void WriteXml (XmlWriter writer)
{
writer.WriteElementString ("Street", Street);
writer.WriteElementString ("PostCode", PostCode);
}
}
JsonSerializer
Getting started
void Main()
{
var p = new Person { Name = "Ian" };
string json = JsonSerializer.Serialize (p,
new JsonSerializerOptions() { WriteIndented = true });
json.Dump();
Person p2 = JsonSerializer.Deserialize<Person> (json);
p2.Dump();
}
public class Person
{
public string Name { get; set; }
}
Serializing child objects
void Main()
{
var home = new Address { Street = "1 Main St.", PostCode = "11235" };
var work = new Address { Street = "4 Elm Ln.", PostCode = "31415" };
var p = new Person { Name = "Ian", HomeAddress = home, WorkAddress = work };
Console.WriteLine (JsonSerializer.Serialize (p,
new JsonSerializerOptions { WriteIndented = true }));
}
public class Address
{
public string Street { get; set; }
public string PostCode { get; set; }
}
public class Person
{
public string Name { get; set; }
public Address HomeAddress { get; set; }
public Address WorkAddress { get; set; }
}
Serializing child objects - object references
void Main()
{
var home = new Address { Street = "1 Main St.", PostCode = "11235" };
var p = new Person { Name = "Ian", HomeAddress = home, WorkAddress = home };
Console.WriteLine (JsonSerializer.Serialize (p,
new JsonSerializerOptions { WriteIndented = true }));
}
public class Address
{
public string Street { get; set; }
public string PostCode { get; set; }
}
public class Person
{
public string Name { get; set; }
public Address HomeAddress { get; set; }
public Address WorkAddress { get; set; }
}
Serializing collections
void Main()
{
var sara = new Person() { Name = "Sara" };
var ian = new Person() { Name = "Ian" };
string json = JsonSerializer.Serialize (new[] { sara, ian },
new JsonSerializerOptions { WriteIndented = true }).Dump ("Json");
Person[] people = JsonSerializer.Deserialize<Person[]>(json);
people.Dump();
}
public class Person
{
public string Name { get; set; }
}
Serializing collections - differently typed objects
void Main()
{
var sara = new Person { Name = "Sara" };
var addr = new Address { Street = "1 Main St.", PostCode = "11235" };
string json = JsonSerializer.Serialize (new object[] { sara, addr },
new JsonSerializerOptions() { WriteIndented = true }).Dump ("JSON");
var deserialized = JsonSerializer.Deserialize<JsonElement[]>(json);
foreach (var element in deserialized)
{
foreach (var prop in element.EnumerateObject())
Console.WriteLine ($"{prop.Name}: {prop.Value}");
Console.WriteLine ("---");
}
}
public class Person
{
public string Name { get; set; }
}
public class Address
{
public string Street { get; set; }
public string PostCode { get; set; }
}
Controlling serialization with attributes
void Main()
{
var p = new Person { Name = "Ian" };
string json = JsonSerializer.Serialize (p,
new JsonSerializerOptions() { WriteIndented = true });
json.Dump();
Person p2 = JsonSerializer.Deserialize<Person> (json);
p2.Dump();
}
public class Person
{
[JsonPropertyName("FullName")]
public string Name { get; set; }
[JsonIgnore]
public decimal NetWorth { get; set; } // Not serialized
}
Customizing data conversion
void Main()
{
var json = @"
{
""Id"":27182,
""Name"":""Sara"",
""Born"":464572800
}";
JsonSerializerOptions opts = new JsonSerializerOptions();
opts.Converters.Add (new UnixTimestampConverter());
var sara = JsonSerializer.Deserialize<Person> (json, opts);
sara.Dump();
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime Born { get; set; }
}
public class UnixTimestampConverter : JsonConverter<DateTime>
{
public override DateTime Read (ref Utf8JsonReader reader, Type type,
JsonSerializerOptions options)
{
if (reader.TryGetInt32(out int timestamp))
return new DateTime (1970, 1, 1).AddSeconds (timestamp);
throw new Exception ("Expected the timestamp as a number.");
}
public override void Write (Utf8JsonWriter writer, DateTime value,
JsonSerializerOptions options)
{
int timestamp = (int)(value - new DateTime(1970, 1, 1)).TotalSeconds;
writer.WriteNumberValue(timestamp);
}
}
Customizing data conversion - default
void Main()
{
var json = @"
{
""Id"":27182,
""Name"":""Sara"",
""Born"":464572800
}";
var sara = JsonSerializer.Deserialize<Person> (json);
sara.Dump();
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
[JsonConverter(typeof(UnixTimestampConverter))]
public DateTime Born { get; set; }
}
public class UnixTimestampConverter : JsonConverter<DateTime>
{
public override DateTime Read (ref Utf8JsonReader reader, Type type,
JsonSerializerOptions options)
{
if (reader.TryGetInt32(out int timestamp))
return new DateTime (1970, 1, 1).AddSeconds (timestamp);
throw new Exception ("Expected the timestamp as a number.");
}
public override void Write (Utf8JsonWriter writer, DateTime value,
JsonSerializerOptions options)
{
int timestamp = (int)(value - new DateTime(1970, 1, 1)).TotalSeconds;
writer.WriteNumberValue(timestamp);
}
}
Options - TrailingCommas
void Main()
{
var dylan = new Person_v3()
{
Name = "Dylan",
LuckyNumbers = new List<int>() { 10, 7 },
Age = 46
};
JsonSerializerOptions opts = new JsonSerializerOptions();
opts.WriteIndented = true;
var json = JsonSerializer.Serialize<Person_v3>(dylan, opts);
"Correct JSON".Dump();
json.Dump();
var brokenJson = json.Replace("7", "7,").Replace("46", "46,");
"Broken JSON".Dump(); // Because of trailing commas we just added
brokenJson.Dump();
"Try to deserialize trailing commas without setting options.".Dump();
try
{
var dylanBroken = JsonSerializer.Deserialize<Person_v3>(brokenJson);
}
catch (JsonException ex)
{
$"As expected, the JSON can't be parsed: {ex.Message}".Dump();
}
"Deserialize with option AllowTrailingCommas = true".Dump();
var dylanCommaTolerant = JsonSerializer.Deserialize<Person_v3>(brokenJson,
new JsonSerializerOptions() { AllowTrailingCommas = true });
dylanCommaTolerant.Dump();
}
class Person_v3
{
public string Name { get; set; }
public List<int> LuckyNumbers { get; set; }
public int Age { get; set; }
}
Options - ReadCommentHandling
void Main()
{
string json = @"
{
""Name"":""Dylan"" // Comment here
/* Another comment here */
}";
var dylan = JsonSerializer.Deserialize<Person>(json,
new JsonSerializerOptions() {
WriteIndented = true,
ReadCommentHandling = JsonCommentHandling.Skip
});
json.Dump();
dylan.Dump();
}
class Person
{
public string Name { get; set; }
}
Options - WriteIndented
void Main()
{
var dylan = new Person()
{
Name = "Dylan",
Birthdate = new DateTime (1996, 9, 7)
};
var json1 = JsonSerializer.Serialize (dylan,
new JsonSerializerOptions { WriteIndented = true });
// WriteIndented defaults to false
var json2 = JsonSerializer.Serialize (dylan);
json1.Dump();
json2.Dump();
}
class Person
{
public string Name { get; set; }
public DateTime Birthdate { get; set; }
}
Options - PropertyNameCaseInsensive
void Main()
{
string json = "{ \"name\":\"Dylan\" }";
var dylan1 = JsonSerializer.Deserialize<Person>(json,
new JsonSerializerOptions() {
WriteIndented = true
});
var dylan2 = JsonSerializer.Deserialize<Person>(json,
new JsonSerializerOptions() {
WriteIndented = true,
PropertyNameCaseInsensitive = true
});
dylan1.Dump();
dylan2.Dump();
}
class Person
{
public string Name { get; set; }
}
Options - PropertyNamingPolicy
void Main()
{
var dylan = new Person { Name = "Dylan" };
var json = JsonSerializer.Serialize (dylan,
new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});
var dylan2 = JsonSerializer.Deserialize<Person> (json,
new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});
dylan.Dump();
json.Dump();
dylan2.Dump();
}
class Person
{
public string Name { get; set; }
}
Options - DictionaryKeyPolicy
void Main()
{
var dict = new Dictionary<string, string>
{
{ "ProgramVersion", "1.2" },
{ "PackageName", "Nutshell" }
};
Console.WriteLine (JsonSerializer.Serialize (dict,
new JsonSerializerOptions()
{
WriteIndented = true,
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase
}));
}
class Person
{
public string Name { get; set; }
public Dictionary<string, string> SportsTeams { get; set; }
}
Options - Encoder - UnsafeRelaxedJsonEscaping
void Main()
{
var dylan = "<b>Dylan & Friends</b>";
JsonSerializer.Serialize (dylan).Dump();
JsonSerializer.Serialize (dylan,
new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
}).Dump();
}
class Person
{
public string Name { get; set; }
public Dictionary<string, string> SportsTeams { get; set; }
}
Options - IgnoreNullValues
void Main()
{
var dylan = new Person { Name = null };
JsonSerializer.Serialize (dylan,
new JsonSerializerOptions { WriteIndented = true }
).Dump("default");
JsonSerializer.Serialize (dylan,
new JsonSerializerOptions
{
WriteIndented = true,
IgnoreNullValues = true
}).Dump ("with IgnoreNullValues");
}
class Person
{
public string Name { get; set; }
public int Age
{
get
{
var age = DateTime.Today.Year - Birthdate.Year;
if (Birthdate.Date > DateTime.Today.AddYears(-age)) age--;
return age;
}
}
public DateTime Birthdate { get; set; }
}
The Binary Serializer
Getting started
void Main()
{
Person p = new Person { Name = "George", Age = 25 };
IFormatter formatter = new BinaryFormatter();
using (FileStream s = File.Create ("serialized.bin"))
formatter.Serialize (s, p);
using (FileStream s = File.OpenRead ("serialized.bin"))
{
Person p2 = (Person)formatter.Deserialize (s);
Console.WriteLine (p2.Name + " " + p2.Age); // George 25
}
}
[Serializable]
public sealed class Person
{
public string Name;
public int Age;
}
[NonSerialized]
void Main()
{
Person p = new Person { Name = "George", Age = 25 };
IFormatter formatter = new BinaryFormatter();
using (FileStream s = File.Create ("serialized.bin"))
formatter.Serialize (s, p);
using (FileStream s = File.OpenRead ("serialized.bin"))
{
Person p2 = (Person)formatter.Deserialize (s);
Console.WriteLine (p2.Name + " " + p2.Age); // George 25
}
}
[Serializable] public sealed class Person
{
public string Name;
[NonSerialized] public int Age;
}
[OnDeserializing] and [OnDeserialized]
void Main()
{
Person p = new Person { Name = "George", DateOfBirth = new DateTime (1990, 1, 1) };
IFormatter formatter = new BinaryFormatter();
using (FileStream s = File.Create ("serialized.bin"))
formatter.Serialize (s, p);
using (FileStream s = File.OpenRead ("serialized.bin"))
{
Person p2 = (Person)formatter.Deserialize (s);
Console.WriteLine (p2.Name + " " + p2.Age); // George 25
}
}
[Serializable]
public sealed class Person
{
public string Name;
public DateTime DateOfBirth;
[NonSerialized] public int Age;
[NonSerialized] public bool Valid = true;
public Person() { Valid = true; }
[OnDeserialized]
void OnDeserialized (StreamingContext context)
{
TimeSpan ts = DateTime.Now - DateOfBirth;
Age = ts.Days / 365; // Rough age in years
}
}
[OnSerializing] and [OnSerialized]
void Main()
{
var foo = new Foo { Xml = XDocument.Parse ("<test />") };
IFormatter formatter = new BinaryFormatter();
using (FileStream s = File.Create ("serialized.bin"))
formatter.Serialize (s, foo);
using (FileStream s = File.OpenRead ("serialized.bin"))
{
var f2 = (Foo)formatter.Deserialize (s);
f2.Xml.Dump();
}
}
[Serializable]
class Foo
{
[NonSerialized]
public XDocument Xml;
string _xmlString;
[OnSerializing]
void OnSerializing (StreamingContext context) => _xmlString = Xml.ToString();
[OnDeserialized]
void OnDeserialized (StreamingContext context) => Xml = XDocument.Parse (_xmlString);
}
Implementing ISerializable
void Main()
{
var team = new Team ("Team", new Player ("Joe"));
IFormatter formatter = new BinaryFormatter();
using (FileStream s = File.Create ("serialized.bin"))
formatter.Serialize (s, team);
using (FileStream s = File.OpenRead ("serialized.bin"))
{
var team2 = (Team)formatter.Deserialize (s);
team2.Dump();
}
}
[Serializable]
public class Player
{
public readonly string Name;
public Player (string name) => Name = name;
}
[Serializable]
public class Team : ISerializable
{
public readonly string Name;
public readonly ImmutableList<Player> Players;
public Team (string name, params Player[] players)
{
Name = name;
Players = players.ToImmutableList();
}
public virtual void GetObjectData (SerializationInfo si,
StreamingContext sc)
{
si.AddValue ("Name", Name);
si.AddValue ("PlayerData", Players.ToArray());
}
protected Team (SerializationInfo si, StreamingContext sc)
{
Name = si.GetString ("Name");
// Deserialize Players to an array to match our serialization:
Player[] p = (Player[])si.GetValue ("PlayerData", typeof (Player[]));
// Construct a new List using this array:
Players = p.ToImmutableList();
}
}