I feel like at some point in every project you find yourself needing to serialise an object. My immediate go-to is JSON: whether it’s being send to another system or saved to disk, the magicians at Newtonsoft have made it super-easy with JSON. However, when I had a requirement to use XML (because I’d be damned to use CSV), the results looked somewhat messier than I expected.
Here’s what I discovered when serialising an object to XML, in three flavours.
Let’s get started.
0. Setup
This is the demo class I’m using for testing.
using Newtonsoft.Json;
public class ClassA
{
[JsonProperty("Prop1")]
public int Prop1 { get; set; }
[JsonProperty("Prop2")]
public string Prop2 { get; set; }
[JsonProperty("Prop3")]
public int? Prop3 { get; set; }
[JsonProperty("Prop4")]
public bool Prop4 { get; set; }
}
ClassA ob = new ClassA() {
Prop1 = 200,
Prop2 = "hello world",
Prop3 = null,
Prop4 = true
};
1. Vanilla (aka XmlSerializer)
This is the textbook answer using XmlSerializer.
protected string serialiseToXML(Type type, object ob)
{
StringWriter sw = new StringWriter();
XmlSerializer xsz = new XmlSerializer(type);
xsz.Serialize(sw, ob);
return sw.ToString();
}
The result is good but has lengthy namespace attributes.
<?xml version="1.0" encoding="utf-16"?>
<ClassA xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Prop1>200</Prop1>
<Prop2>hello world</Prop2>
<Prop3 xsi:nil="true" />
<Prop4>true</Prop4>
</ClassA>
2. Banana (aka XmlSerializer with Namespace)
This is what you find all over the Web when you want to hide the xmlns.
protected string serialiseToXMLNS(Type type, object ob)
{
StringWriter sw = new StringWriter();
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
XmlSerializer xsz = new XmlSerializer(type);
xsz.Serialize(sw, ob, ns);
return sw.ToString();
}
The result is good but I still see namespaces.
<?xml version="1.0" encoding="utf-16"?>
<ClassA>
<Prop1>200</Prop1>
<Prop2>hello world</Prop2>
<Prop3 d2p1:nil="true" xmlns:d2p1="http://www.w3.org/2001/XMLSchema-instance" />
<Prop4>true</Prop4>
</ClassA>
3. Chocolate (aka JsonConvert)
This is a solution some idiot came up with by serialising the object to JSON and then to XML. There’s an added step because XML expects a single root node.
protected string serialiseToXMLByJSON(Type type, object ob)
{
object newOb = new { root = ob };
string rv = JsonConvert.SerializeObject(newOb);
XmlDocument doc = JsonConvert.DeserializeXmlNode(rv);
return string.Concat("<", type.Name , ">", doc.FirstChild.InnerXml, "</", type.Name, ">");
}
The result is minimised.
<ClassA><Prop1>200</Prop1><Prop2>hello world</Prop2><Prop3 /><Prop4>true</Prop4></ClassA>
A. Deserialise for (1) and (2)
I didn’t want to show code for serialising an object to XML without including code to deserialise back to the object.
StringReader sr = new StringReader(xml);
XmlSerializer xsz = new XmlSerializer(typeof(ClassA));
ClassA ob = (ClassA)xsz.Deserialize(sr);
B. Deserialise for (3)
Remember that root node that XML needs? Yes, we have to handle that when deserialising the XML to JSON then to object.
protected ClassA deserialiseXMLJSONObject(string xml)
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
// remove root node
string json = JsonConvert.SerializeXmlNode(doc.FirstChild);
int n = json.IndexOf(":") + 1;
json = json.Substring(n, (json.Length - n - 1));
return JsonConvert.DeserializeObject<ClassA>(json);
}
I hope someone finds this useful.
Posted on Sat 23rd Feb 2019
Modified on Sun 13th Mar 2022