Did you know about the InvalidOperationException on the System namespace on .NET?. Specifically, what happens on .NET serializations with cross referenced or circular referenced objects in .NET?. Object Oriented programming is complex, and objects can hold complex structures, including those kind of object references, but in .NET you have prohibited to use that kind of object references, cross and circular ones, if you want to serialize an object. Instead of prohibition, pyxser — my Python-Object to XML serializer — allows you to create that kind of references, preserving its original reference across the serialization and deserialization process.
Let’s take a look on the .NET serialization model. It allows you to create custom XML elements and custom attributes, but does it works with complex objects?. The problem with the .NET serialization model is the fact that it can’t handle cross refereces or circular references, since the serialization model do not consider that kind of object references. If you try to serialize an object with that kind of reference, you will get a pretty exception like this:
Unhandled Exception: System.InvalidOperationException: There was an error generating the XML document. ---> System.InvalidOperationException: A circular reference was detected while serializing an object of type TestData. at System.Xml.Serialization.XmlSerializationWriter.WriteStartElement(String name, String ns, Object o, Boolean writePrefixed, XmlSerializerNamespaces xmlns) at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterTestDa
Here is an example of cross referenced object in .NET, so you can verify yourself that you must not use that kind of references on .NET:
using System; using System.Xml.Serialization; using System.IO; [XmlRoot("TestDataXml")] public class TestData { private int _Identity = 0; [XmlElement("Identity")] public int Identity { get { return this._Identity; } set { this._Identity = value; } } private string _Name = ""; [XmlElement("DataName")] public string Name { get { return this._Name; } set { this._Name = value; } } private string _IgnoreMe = ""; [XmlIgnore] public string IgnoreMe { get { return this._IgnoreMe; } set { this._IgnoreMe = value; } } private CrossData _CrossReference; [XmlElement("CircularReference")] public CrossData CrossReference { get { return this._CrossReference; } set { this._CrossReference = value; } } public TestData() { } } [XmlRoot("TestDataXml")] public class CrossData { private int _Identity = 0; [XmlElement("Identity")] public int Identity { get { return this._Identity; } set { this._Identity = value; } } private string _Name = ""; [XmlElement("DataName")] public string Name { get { return this._Name; } set { this._Name = value; } } private string _IgnoreMe = ""; [XmlIgnore] public string IgnoreMe { get { return this._IgnoreMe; } set { this._IgnoreMe = value; } } private TestData _CrossReference; [XmlElement("CircularReference")] public TestData CrossReference { get { return this._CrossReference; } set { this._CrossReference = value; } } public CrossData() { } } public class MainTest { public static void Main(string[] args) { TestData test = new TestData(); test.Identity = 1; test.Name = "Cross Reference 1"; test.IgnoreMe = "Ignored"; CrossData cross = new CrossData(); cross.Identity = 2; cross.Name = "Cross Reference 2"; test.CrossReference = cross; cross.CrossReference = test; XmlSerializer serializer = new XmlSerializer(test.GetType()); MemoryStream stream = new MemoryStream(512); serializer.Serialize(stream, test); Console.WriteLine(stream.ToString()); stream.Close(); } }
And here is an example of circular referenced object in .NET, so you can verify yourself that you must not use that kind of references on .NET:
using System; using System.Xml.Serialization; using System.IO; [XmlRoot("TestDataXml")] public class TestData { private int _Identity = 0; [XmlElement("Identity")] public int Identity { get { return this._Identity; } set { this._Identity = value; } } private string _Name = ""; [XmlElement("DataName")] public string Name { get { return this._Name; } set { this._Name = value; } } private string _IgnoreMe = ""; [XmlIgnore] public string IgnoreMe { get { return this._IgnoreMe; } set { this._IgnoreMe = value; } } private TestData _CircularRefence; [XmlElement("CircularReference")] public TestData CircularReference { get { return this._CircularRefence; } set { this._CircularRefence = value; } } public TestData() { } } public class MainTest { public static void Main(string[] args) { TestData test = new TestData(); test.Identity = 1; test.Name = "Circular Reference"; test.IgnoreMe = "Ignored"; test.CircularReference = test; XmlSerializer serializer = new XmlSerializer(test.GetType()); MemoryStream stream = new MemoryStream(512); serializer.Serialize(stream, test); Console.WriteLine(stream.ToString()); stream.Close(); } }
I really can’t stand with all this software quality on what software design refers. What happens in other levels of .NET, is that really safe as they claims?. Do they know about compiler construction techniques and which types of data can be encapsulated on XML. Possibly not… Here you have a simple example with pyxser, which does the both tasks, since the pyxser serialization model allows you to serialize and deserialize that kind of object refereces:
#!/usr/bin/env python # # # import pyxser import testmod.testmod as t class TestData(): m1 = "hola" m2 = "chao" m3 = None def __init__(self): m1 = "chao" m2 = "hola" m3 = self class TestCross(): m1 = "chao" m2 = "hola" m3 = None def __init__(self): m1 = "chao" m2 = "hola" m3 = self def main(): x = t.TestData() x.m1 = "hola" x.m2 = "chao" x.m3 = x print x circular = pyxser.serialize(obj = x, enc = "utf-8", depth = 50) print "---8<------ CIRCULAR REFERENCE ----8<---" print circular y = t.TestCross() y.m1 = "chao" y.m2 = "hola" y.m3 = x x.m3 = y cross = pyxser.serialize(obj = y, enc = 'utf-8', depth = 50) print "---8<------ CROSS REFERENCE ----8<---" print cross if __name__ == "__main__": main()
As you will see in the next piece of XML, the first serialization gives me the next result:
<?xml version="1.0" encoding="utf-8"?> <pyxs:obj xmlns:pyxs="http://projects.coder.cl/pyxser/model/" version="1.0" type="TestData" module="testmod.testmod" objid="id3077395276"> <pyxs:prop type="str" name="m1">hola</pyxs:prop> <pyxs:obj module="testmod.testmod" type="TestData" name="m3" objref="id3077395276"/> <pyxs:prop type="str" name="m2">chao</pyxs:prop> </pyxs:obj>
The second one brings me the next result:
<?xml version="1.0" encoding="utf-8"?> <pyxs:obj xmlns:pyxs="http://projects.coder.cl/pyxser/model/" version="1.0" type="TestCross" module="testmod.testmod" objid="id3077395180"> <pyxs:prop type="str" name="m1">chao</pyxs:prop> <pyxs:obj module="testmod.testmod" type="TestData" name="m3" objid="id3077395276"> <pyxs:prop type="str" name="m1">hola</pyxs:prop> <pyxs:obj module="testmod.testmod" type="TestCross" name="m3" objref="id3077395180"/> <pyxs:prop type="str" name="m2">chao</pyxs:prop> </pyxs:obj> <pyxs:prop type="str" name="m2">hola</pyxs:prop> </pyxs:obj>
The solution is done using the proper ID and IDREF attributes on the pyxs:obj element. So you can serialize complex object trees without problems. There is no trick by using those kind of elements, and are present on markup languages from SGML, and anyone with a minimum knowledge about XML knows about them. And this is only one point on a large set of points against .NET that makes me leave the development under that platform. Do not come here with your pretty cool story about how nice is the development under .NET.
[...] a small set of objects and one circular reference to its container on the collection, the serialization will fail, and you will require more hours to finish that task and send the collection properly. Many times [...]
[...] between Python, .NET and Java platforms using XML serialized objects, probably under Web Services. Remember that .NET throws a InvalidOperationException exception with the error message “A circular [...]
I tested the python code excerpt shown above in the article. One thing I noticed was that when I tried to deserialize the cross string(in the second case) back to an object, I didn’t get the same circular relation as the original relation. I used code like this to test this feature:
I got the following results:
The relation from y to x was successfully restored while the relation from x to y, the other way around, wasn’t. This proves that cross relations aren’t restored upon deserialization. Please correct me if I’ve missed anything.