This post is intended to be a quick and dirty cheatsheet outlining how to turn an object into a string and back using the four serializers I use most: DataContractSerializer, XmlSerializer, BinaryFormatter, and DataContractJsonSerializer. There’s absolutely nothing special about this information as it’s all freely available on the web in a million different forms – I simply wanted this all in one place as I can easily find it the next time I need it.
Ugly WCF Xml (DataContractSerializer):
This is the default serializer used by WCF. If you have a WCF service and you haven’t went out of your way to change the underlying serializer, then this is the serializer you’re using. It’s an opt-in model where only the members you declare as serializable (via the [DataMember] attribute) will be serialized. The DataContractSerializer is significantly faster than the XmlSerializer, but it gives you very little control over how the resulting XML text is formatted. For example, if you like your properties to be XML attributes – too bad, everything serialized by the DCS ends up as XML elements. It produces very verbose XML that’s littered with machine generated XML namespaces that’s very hard on the eyes. The good news is the XML it produces is very compression friendly. So, if you’re worried about that bloat, definitely check out GZip compression. I’ve yet to find a way to make the XML easier on the eyes, but check out the Closing Thoughts section at the end.
The subject:
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Text;
using System.Xml;
using NUnit.Framework;
[DataContract]
public class DcsSubject
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public DcsSubject InnerSubject { get; set; }
[DataMember]
public DcsSubjectList InnerSubjects { get; set; }
// no [DataMember] -> hidden
public string Hidden { get; set; }
}
[CollectionDataContract]
public class DcsSubjectList : List<DcsSubject>
{
}
Serializing to text:
[Test]
public void Serialize_To_Text()
{
DcsSubject subject = new DcsSubject();
subject.Id = 2;
subject.Name = "foo";
subject.InnerSubject = new DcsSubject();
subject.InnerSubject.Id = 3;
subject.InnerSubject.Name = "bar";
subject.Hidden = "hello world";
subject.InnerSubjects = new DcsSubjectList { new DcsSubject { Id = 4, Name = "Baz" } };
string text;
StringBuilder builder = new StringBuilder();
DataContractSerializer serializer = new DataContractSerializer(typeof(DcsSubject));
using (XmlWriter writer = XmlWriter.Create(builder))
{
serializer.WriteObject(writer, subject);
writer.Flush();
text = builder.ToString();
Console.WriteLine(text);
}
Assert.IsFalse(string.IsNullOrEmpty(text));
}
Passed:
------ Test started: Assembly: SerializationCheatsheet.dll ------
<?xml version="1.0" encoding="utf-16"?><DcsSubject xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/SerializationCheatsheet"><Id>2</Id><InnerSubject><Id>3</Id><InnerSubject i:nil="true" /><InnerSubjects i:nil="true" /><Name>bar</Name></InnerSubject><InnerSubjects><DcsSubject><Id>4</Id><InnerSubject i:nil="true" /><InnerSubjects i:nil="true" /><Name>Baz</Name></DcsSubject></InnerSubjects><Name>foo</Name></DcsSubject>
1 passed, 0 failed, 0 skipped, took 1.85 seconds (NUnit 2.5.5).
Deserializing from text:
[Test]
public void Deserialize_From_Text()
{
DataContractSerializer serializer = new DataContractSerializer(typeof(DcsSubject));
const string xml = "<?xml version=\"1.0\" encoding=\"utf-16\"?><DcsSubject xmlns=\"http://schemas.datacontract.org/2004/07/SerializationCheatsheet\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"><Id>2</Id><InnerSubject><Id>3</Id><InnerSubject i:nil=\"true\"/><InnerSubjects i:nil=\"true\"/><Name>bar</Name></InnerSubject><InnerSubjects><DcsSubject><Id>4</Id><InnerSubject i:nil=\"true\"/><InnerSubjects i:nil=\"true\"/><Name>Baz</Name></DcsSubject></InnerSubjects><Name>foo</Name></DcsSubject>";
DcsSubject subject;
using (MemoryStream stream = new MemoryStream(Encoding.Unicode.GetBytes(xml)))
{
subject = (DcsSubject)serializer.ReadObject(stream);
}
Assert.IsNotNull(subject);
Assert.AreEqual(subject.Id, 2);
Assert.IsNotNull(subject.Name);
Assert.IsNotNull(subject.InnerSubject);
Assert.IsNotNull(subject.InnerSubjects);
}
Passed:
------ Test started: Assembly: SerializationCheatsheet.dll ------
1 passed, 0 failed, 0 skipped, took 0.90 seconds (NUnit 2.5.5).
Pretty Xml (XmlSerializer)
This is the legacy serializer used by the ASMX web services stack (pre-WCF). It gives you the most control over shaping the resulting XML, but its entirely reflection based and it is significantly slower than the DataContractSerializer.
The subject:
The XmlSerializer is an opt-out model where all properties will be serialized unless the property is explicitly hidden with the [XmlIgnore] attribute. By default it will serialize all properties into XML elements. If you’d like to tidy up the XML a bit and make the primitive properties serialize to XML attributes, you can apply the [XmlAttribute] attribute to your properties. All null properties will simply not be present in the resulting XML (unlike the DCS where nil=”true” is appended).
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using NUnit.Framework;
public class XmlSerializerSubject
{
[XmlAttribute]
public int Id { get; set; }
public string Name { get; set; }
public XmlSerializerSubject InnerSubject { get; set; }
[XmlArray(ElementName = "InnerSubjects")]
public XmlSerializerSubjectList InnerSubjects { get; set; }
[XmlIgnore]
public string Hidden { get; set; } // don't serialize this
}
public class XmlSerializerSubjectList : List<XmlSerializerSubject>
{
}
Serialize to text:
[Test]
public void Serialize_To_Text()
{
XmlSerializerSubject subject = new XmlSerializerSubject();
subject.Id = 2;
subject.Name = "foo";
subject.InnerSubject = new XmlSerializerSubject();
subject.InnerSubject.Id = 3;
subject.InnerSubject.Name = "bar";
subject.Hidden = "hello world";
subject.InnerSubjects = new XmlSerializerSubjectList { new XmlSerializerSubject { Id = 4, Name = "Baz" } };
XmlSerializer serializer = new XmlSerializer(typeof(XmlSerializerSubject));
string text;
StringBuilder builder = new StringBuilder();
using ( XmlWriter writer = XmlWriter.Create(builder, new XmlWriterSettings{Encoding = Encoding.Unicode, OmitXmlDeclaration = false, Indent = true}))
{
serializer.Serialize(writer, subject);
text = builder.ToString();
Console.WriteLine(text);
}
Assert.IsFalse(string.IsNullOrEmpty(text));
}
Passed:
------ Test started: Assembly: SerializationCheatsheet.dll ------
<?xml version="1.0" encoding="utf-16"?>
<XmlSerializerSubject xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Id="2">
<Name>foo</Name>
<InnerSubject Id="3">
<Name>bar</Name>
</InnerSubject>
<InnerSubjects>
<XmlSerializerSubject Id="4">
<Name>Baz</Name>
</XmlSerializerSubject>
</InnerSubjects>
</XmlSerializerSubject>
1 passed, 0 failed, 0 skipped, took 1.09 seconds (NUnit 2.5.5).
Deserialize from text:
[Test]
public void Deserialize_From_Text()
{
XmlSerializer serializer = new XmlSerializer(typeof(XmlSerializerSubject));
StringBuilder xml = new StringBuilder();
xml.AppendLine("<?xml version=\"1.0\" encoding=\"utf-16\"?>");
xml.AppendLine("<XmlSerializerSubject xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" Id=\"2\">");
xml.AppendLine(" <Name>foo</Name>");
xml.AppendLine(" <InnerSubject Id=\"3\">");
xml.AppendLine(" <Name>bar</Name>");
xml.AppendLine(" </InnerSubject>");
xml.AppendLine(" <InnerSubjects>");
xml.AppendLine(" <XmlSerializerSubject Id=\"4\">");
xml.AppendLine(" <Name>Baz</Name>");
xml.AppendLine(" </XmlSerializerSubject>");
xml.AppendLine(" </InnerSubjects>");
xml.AppendLine("</XmlSerializerSubject>");
XmlSerializerSubject subject;
using (MemoryStream stream = new MemoryStream(Encoding.Unicode.GetBytes(xml.ToString())))
{
subject = (XmlSerializerSubject)serializer.Deserialize(stream);
}
Assert.IsNotNull(subject);
Assert.AreEqual(subject.Id, 2);
Assert.IsNotNull(subject.Name);
Assert.IsNotNull(subject.InnerSubject);
Assert.IsNotNull(subject.InnerSubjects);
}
Passed:
------ Test started: Assembly: SerializationCheatsheet.dll ------
1 passed, 0 failed, 0 skipped, took 1.07 seconds (NUnit 2.5.5).
Binary (BinaryFormatter):
I typically don’t see binary serialization used much – especially in this fashion – but there are times where it’s needed. A common scenario I see is when fat client apps want to save off a memento of state to be loaded on the next app startup. Serializing and deserializing in this fashion can be useful.
The subject:
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using NUnit.Framework;
[Serializable]
public class BinarySubject
{
public int Id { get; set; }
public string Name { get; set; }
public BinarySubject InnerSubject { get; set; }
public BinarySubjectList InnerSubjects { get; set; }
public string Hidden { get; set; }
}
[Serializable]
public class BinarySubjectList : List<BinarySubject>
{
}
Serializing to text:
Be very careful with your choice of encoding. I’m using ASCII here so that I can convert the Base64 string back into a byte array and ultimately deserialize it back into an instance of my subject. By the way - this wouldn’t work with Unicode.
[Test]
public void Serialize_To_Text()
{
BinarySubject subject = new BinarySubject();
subject.Id = 2;
subject.Name = "foo";
subject.InnerSubject = new BinarySubject();
subject.InnerSubject.Id = 3;
subject.InnerSubject.Name = "bar";
subject.Hidden = "hello world";
subject.InnerSubjects = new BinarySubjectList { new BinarySubject { Id = 4, Name = "Baz" } };
string text;
using (MemoryStream stream = new MemoryStream())
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, subject);
stream.Position = 0;
using (StreamReader reader = new StreamReader(stream))
{
byte[] bytes = Encoding.ASCII.GetBytes(reader.ReadToEnd());
text = Convert.ToBase64String(bytes); // convert it to something legible
Console.WriteLine(text);
}
}
Assert.IsFalse(string.IsNullOrEmpty(text));
}
Passed:
------ Test started: Assembly: SerializationCheatsheet.dll ------
AAEAAAA/Pz8/AQAAAAAAAAAMAgAAAE5TZXJpYWxpemF0aW9uQ2hlYXRzaGVldCwgVmVyc2lvbj0xLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPW51bGwFAQAAACVTZXJpYWxpemF0aW9uQ2hlYXRzaGVldC5CaW5hcnlTdWJqZWN0BQAAABM8SWQ+a19fQmFja2luZ0ZpZWxkFTxOYW1lPmtfX0JhY2tpbmdGaWVsZB08SW5uZXJTdWJqZWN0PmtfX0JhY2tpbmdGaWVsZB48SW5uZXJTdWJqZWN0cz5rX19CYWNraW5nRmllbGQXPEhpZGRlbj5rX19CYWNraW5nRmllbGQAAQQEAQglU2VyaWFsaXphdGlvbkNoZWF0c2hlZXQuQmluYXJ5U3ViamVjdAIAAAApU2VyaWFsaXphdGlvbkNoZWF0c2hlZXQuQmluYXJ5U3ViamVjdExpc3QCAAAAAgAAAAIAAAAGAwAAAANmb28JBAAAAAkFAAAABgYAAAALaGVsbG8gd29ybGQBBAAAAAEAAAADAAAABgcAAAADYmFyCgoKBQUAAAApU2VyaWFsaXphdGlvbkNoZWF0c2hlZXQuQmluYXJ5U3ViamVjdExpc3QDAAAADUxpc3RgMStfaXRlbXMMTGlzdGAxK19zaXplD0xpc3RgMStfdmVyc2lvbgQAACdTZXJpYWxpemF0aW9uQ2hlYXRzaGVldC5CaW5hcnlTdWJqZWN0W10CAAAACAgCAAAACQgAAAABAAAAAQAAAAcIAAAAAAEAAAAEAAAABCVTZXJpYWxpemF0aW9uQ2hlYXRzaGVldC5CaW5hcnlTdWJqZWN0AgAAAAkJAAAADQMBCQAAAAEAAAAEAAAABgoAAAADQmF6CgoKCw==
1 passed, 0 failed, 0 skipped, took 0.79 seconds (NUnit 2.5.5).
JSON (DataContractJsonSerializer):
The DataContractJsonSerializer, like the DataContractSerializer, is an opt-in model. You have to annotate your classes with the members you would like to have serialized. Non-decorated members are not serialized. By the way, the DataContractJsonSerializer is located in System.ServiceModel.Web.dll
The subject:
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;
using NUnit.Framework;
[DataContract]
public class JsonSubject
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public JsonSubject InnerSubject { get; set; }
[DataMember]
public JsonSubjectList InnerSubjects { get; set; }
// no [DataMember] -> hidden
public string Hidden { get; set; }
}
[CollectionDataContract]
public class JsonSubjectList : List<JsonSubject>
{
}
Serializing to Text:
[Test]
public void Serialize_To_Text()
{
JsonSubject subject = new JsonSubject();
subject.Id = 2;
subject.Name = "foo";
subject.InnerSubject = new JsonSubject();
subject.InnerSubject.Id = 3;
subject.InnerSubject.Name = "bar";
subject.Hidden = "hello world";
subject.InnerSubjects = new JsonSubjectList {new JsonSubject {Id = 4, Name = "Baz"}};
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(JsonSubject));
string text;
using (MemoryStream stream = new MemoryStream())
{
serializer.WriteObject(stream, subject);
stream.Position = 0;
using (StreamReader reader = new StreamReader(stream))
{
text = reader.ReadToEnd();
Console.WriteLine(text);
}
}
Assert.IsFalse(string.IsNullOrEmpty(text));
}
Passed:
------ Test started: Assembly: SerializationCheatsheet.dll ------
{"Id":2,"InnerSubject":{"Id":3,"InnerSubject":null,"InnerSubjects":null,"Name":"bar"},"InnerSubjects":[{"Id":4,"InnerSubject":null,"InnerSubjects":null,"Name":"Baz"}],"Name":"foo"}
1 passed, 0 failed, 0 skipped, took 0.41 seconds (NUnit 2.5.5).
Deserializing from Text:
[Test]
public void Deserialize_From_Text()
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(JsonSubject));
const string json =
"{\"Id\":2,\"InnerSubject\":{\"Id\":3,\"InnerSubject\":null,\"InnerSubjects\":null,\"Name\":\"bar\"},\"InnerSubjects\":[{\"Id\":4,\"InnerSubject\":null,\"InnerSubjects\":null,\"Name\":\"Baz\"}],\"Name\":\"foo\"}";
JsonSubject subject;
using (MemoryStream stream = new MemoryStream(Encoding.Unicode.GetBytes(json)))
{
subject = (JsonSubject)serializer.ReadObject(stream);
}
Assert.IsNotNull(subject);
Assert.AreEqual(subject.Id, 2);
Assert.IsNotNull(subject.Name);
Assert.IsNotNull(subject.InnerSubject);
Assert.IsNotNull(subject.InnerSubjects);
}
Passed:
------ Test started: Assembly: SerializationCheatsheet.dll ------
1 passed, 0 failed, 0 skipped, took 0.52 seconds (NUnit 2.5.5).
Closing Thoughts
I like to make my service contracts DataContractSerializer, XmlSerializer, and BinaryFormatter compatible. That way, I know that my entities are serializable in all the ways I care about – i.e. the DCS for on-the-wire runtime serialization, XmlSerializer for logging and diagnostics (when I’m looking at logs I want to see pretty XML), and BinaryFormatter for caching. Most caching architectures end up using a distributed cache of some sort where your objects are getting serialized and persisted on that remote store. In that case, usually you’ll end up having to deal with the BinaryFormatter at some point. So, I make it a rule of thumb to decorate my objects in a fashion that supports all of these scenarios right out of the date.
Supporting JSON is a niche thing for me – I only care about it when I’m working with web apps, though I like the way it looks and might start considering using it for diagnostics. By the way, a subject that supports all of these serialization modes I’m taking about here would look something like this:
[DataContract, Serializable]
public class DcsSubject
{
[DataMember, XmlAttribute]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public DcsSubject InnerSubject { get; set; }
[DataMember, XmlArray]
public DcsSubjectList InnerSubjects { get; set; }
[XmlIgnore]
public string Hidden { get; set; }
}
[CollectionDataContract, Serializable]
public class DcsSubjectList : List<DcsSubject>
{
}