.Net Xml Serialization Gotchas

Scenarios where Xml Serialization fail in .NET

I'm thinking mainly of XmlSerializer here:

  • it is limited to tree-like data; it can't handle full object graphs
  • it is limited to public members, on public classes
  • it can't really do much with object members
  • it has some weaknesses around generics
  • like many serializers, it won't touch instance properties on a collection (bad practice in the first place)
  • xml simply isn't always a good choice for large data (not least, for performance)
  • requires a public parameterless constructor

DataContractSerializer solves some of these, but has its own limitations:

  • it can't handle values in attributes
  • requires .NET 3.0 (so not much use in 2.0)

Xml Serialization Issues in Ordering and XMLRoot attributes

The first issue is on the XmlRoot element. I expected version attribute to appear inside the XMLRoot tag but it does not.

As explained in Introducing XML Serialization, XmlSerializer will not serialize a const member even when public:

XML serialization serializes only the public fields and property values of an object into an XML stream. XML serialization does not include type information.

<snip>

XML serialization does not convert methods, indexers, private fields, or read-only properties (except read-only collections). To serialize all an object's fields and properties, both public and private, use the DataContractSerializer instead of XML serialization.

The easiest way to work around this is to add a surrogate property for the version, like so:

public const string version = "D";

[XmlAttribute("version")]
[System.ComponentModel.Browsable(false), System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never), System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
public string Version { get { return version; } set { /* Do nothing */ } }

The property has to be public, but you can decrease its visibility via the attributes Browsable, EditorBrowsable and DebuggerBrowsable.

The second issue is the order of elements (ok it's not a big issue since tags are not order dependent) I tried to use XmlElement(Order=nn) attribute to force it but I just got a number of Reflection exceptions.

You didn't provide an example of this, but I was able to reproduce the following issue. If I set XmlArrayAttribute.Order on some but not all of the collection members of SchematicExport, I got the following exception:

System.InvalidOperationException: There was an error reflecting type 'SchematicExport'. 
---> System.InvalidOperationException: Inconsistent sequencing: if used on one of the class's members, the 'Order' property is required on all particle-like members, please explicitly set 'Order' using XmlElement, XmlAnyElement or XmlArray custom attribute on class member 'nets'.
at System.Xml.Serialization.XmlReflectionImporter.InitializeStructMembers(StructMapping mapping, StructModel model, Boolean openModel, String typeName, RecursionLimiter limiter)
at System.Xml.Serialization.XmlReflectionImporter.ImportStructLikeMapping(StructModel model, String ns, Boolean openModel, XmlAttributes a, RecursionLimiter limiter)
at System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(TypeModel model, String ns, ImportContext context, String dataType, XmlAttributes a, Boolean repeats, Boolean openModel, RecursionLimiter limiter)

Demo fiddle #1 here.

The solution is to follow the advice in the exception message and apply the order attribute to all serializable members in the class.

Thus SchematicExport should look something like:

[XmlRoot(ElementName = "export")]
public partial class SchematicExport
{
public const string version = "D";

[XmlAttribute("version")]
[System.ComponentModel.Browsable(false), System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never), System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
public string Version { get { return version; } set { /* Do nothing */ } }

[XmlElement(Order = 1)]
public Design design { get; set; }

[XmlArray(Order = 2)]
[XmlArrayItem(typeof(Component), ElementName = "comp")]
public List<Component> components;

[XmlArray(Order = 3)]
[XmlArrayItem(typeof(LibPart), ElementName = "libpart")]
public List<LibPart> libparts;

[XmlArray(Order = 4)]
[XmlArrayItem(typeof(Library), ElementName = "library")]
public List<Library> libraries;

[XmlArray(Order = 5)]
[XmlArrayItem(typeof(Net), ElementName = "net")]
public List<Net> nets;
}

Note that setting the element order will not only reorder the elements during serialization, but also require that they be in that order during deserialization.

Incidentally, in SchematicExport.Serialize(string) you need to close your StreamWriter. The easiest way to do this is via a using statement:

public void Serialze(string filename)
{
XmlSerializer _xmlSerializer = new XmlSerializer(typeof(SchematicExport));
// FIXED ensure the file is closed.
using (var _textWriter = new StreamWriter(filename))
{
_xmlSerializer.Serialize(_textWriter, this);
}
}

The first version of this method shown in your question does not do this.

Demo fiddle #2 here.

Why isn't my public property serialized by the XmlSerializer?

As mentioned, most properties must have both a getter and setter; the main exception to this is lists - for example:

private readonly List<Foo> bar = new List<Foo>();
public List<Foo> Bar {get { return bar; } } // works fine

which will work fine; however, if XmlSerializer finds a setter - it demands that it is public; the following will not work:

public List<Foo> Bar {get; private set;} // FAIL

Other reasons it might not serialize:

  • it isn't public with get and set (or is readonly for a field)
  • it has a [DefaultValue] attribute, and is with that value
  • it has a public bool ShouldSerializeFoo() method that returned false
  • it has a public bool FooSpecified {get;set;} property or field that returned false
  • it is marked [XmlIgnore]
  • it is marked [Obsolete]

Any of these will cause it not to serialize

Using .Net what limitations (if any) are there in using the XmlSerializer?

The XmlSerializer has a few drawbacks.

  1. It must know all the types being serialized. You cannot pass it something by interface that represents a type that the serializer does not know.
  2. It cannot do circular references.
  3. It will serializes the same object multiple times if referenced multiple times in the object graph.
  4. Cannot handle private field serialization.

I (stupidly) wrote my own serializer to get around some of these problems. Don't do that; it is a lot of work and you will find subtle bugs in it months down the road. The only thing I gained in writing my own serializer and formatter was a greater appreciation of the minutia involved in object graph serialization.

I found the NetDataContractSerializer when WCF came out. It does all the stuff from above that XmlSerializer doesn't do. It drives the serialization in a similar fashion to the XmlSerializer. One decorates various properties or fields with attributes to inform the serializer what to serialize. I replaced the custom serializer I had written with the NetDataContractSerializer and was very happy with the results. I would highly recommend it.

Serialization and Deserialization into an XML file, C#

Your result is List<string> and that is not directly serializable. You'll have to wrap it, a minimal approach:

[Serializable]
class Filelist: List<string> { }

And then the (De)Serialization goes like:

Filelist data = new Filelist(); // replaces List<string>

// fill it

using (var stream = File.Create(@".\data.xml"))
{
var formatter = new System.Runtime.Serialization.Formatters.Soap.SoapFormatter();
formatter.Serialize(stream, data);
}

data = null; // lose it

using (var stream = File.OpenRead(@".\data.xml"))
{
var formatter = new System.Runtime.Serialization.Formatters.Soap.SoapFormatter();
data = (Filelist) formatter.Deserialize(stream);
}

But note that you will not be comparing the XML in any way (not practical). You will compare (deserialzed) List instances. And the XML is SOAP formatted, take a look at it. It may not be very useful in another context.

And therefore you could easily use a different Formatter (binary is a bit more efficient and flexible).

Or maybe you just want to persist the List of files as XML. That is a different question.

Xml Serialization in C#

You may want to have a look on the overloaded method for serializing an object:

default serialization

defining namespaces for serialization

As mentioned there, you can define XmlSerializerNamespaces with the following code lines:

    // Create an XmlSerializerNamespaces object.
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();

// Add two prefix-namespace pairs.
ns.Add("inventory", "http://www.cpandl.com");
ns.Add("money", "http://www.cohowinery.com");

Hope that helps.

Changing type of element in XML Serialization

The reason you get the error is that, during XmlSerializer code generation, the code generator doesn't understand that the two potential NAME elements on Cat will never be simultaneously serialized, so throws the exception.

Instead, you can apply XmlAnyElementAttribute to a virtual property returning an XElement, then manually create and return an appropriate XElement for the name for each class in the hierarchy:

[XmlInclude(typeof(Cat))]
public class Animal
{
[XmlIgnore]
public string Name { get; set; }

[XmlAnyElement]
public virtual XElement XmlName
{
get
{
return Name == null ? null : new XElement("NAME", Name);
}
set
{
Name = (value == null ? null : value.Value);
}
}
}

public class Cat : Animal
{
// Must be cached as per https://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer%28v=vs.110%29.aspx
static XmlSerializer nameSerializer;

static Cat()
{
nameSerializer = new XmlSerializer(typeof(NameAndType), new XmlRootAttribute("NAME"));
}

[XmlIgnore]
public NameAndType Name2 { get; set; }

[XmlAnyElement]
public override XElement XmlName
{
get
{
return (Name2 == null ? null : XObjectExtensions.SerializeToXElement(Name2, nameSerializer, true));
}
set
{
Name2 = (value == null ? null : XObjectExtensions.Deserialize<NameAndType>(value, nameSerializer));
}
}
}

Using the extension methods:

public static class XObjectExtensions
{
public static T Deserialize<T>(this XContainer element)
{
return element.Deserialize<T>(new XmlSerializer(typeof(T)));
}

public static T Deserialize<T>(this XContainer element, XmlSerializer serializer)
{
using (var reader = element.CreateReader())
{
object result = serializer.Deserialize(reader);
if (result is T)
return (T)result;
}
return default(T);
}

public static XElement SerializeToXElement<T>(this T obj)
{
return obj.SerializeToXElement(new XmlSerializer(obj.GetType()), true);
}

public static XElement SerializeToXElement<T>(this T obj, XmlSerializer serializer, bool omitStandardNamespaces)
{
var doc = new XDocument();
using (var writer = doc.CreateWriter())
{
XmlSerializerNamespaces ns = null;
if (omitStandardNamespaces)
(ns = new XmlSerializerNamespaces()).Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
serializer.Serialize(writer, obj, ns);
}
var element = doc.Root;
if (element != null)
element.Remove();
return element;
}
}

Which, for a List<Animal>, produces XML like this:

<ArrayOfAnimal xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Animal>
<NAME>duck</NAME>
</Animal>
<Animal xsi:type="Cat">
<NAME>
<Name>Smokey</Name>
<Type>Siamese</Type>
</NAME>
</Animal>
</ArrayOfAnimal>


Related Topics



Leave a reply



Submit