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(); } }