I have recently been doing a lot of work with the DataContractJsonSerializer and ended up writing some code that others may find useful. I had a base class which had some behaviors. Anyone deriving from my base class needed to set some properties. At first I did this in the constructor of the base class, like this:
public class DerivedObject : AbstractObject
{
public DerivedObject()
: base("derived object name!", "1001")
{
}
}
This pattern ensured that anyone deriving from my Abstract class would be forced to set these properties
However, I learned that, during deserialization, the constructor never got called. This killed my design. But, I learned about an attribute called OnDeserializing which I could decorate a method with. Basically, this attribute forces a method to get called upon deserialization. So, I changed my design to use a custom attribute, which had the same effect as the constructor. The signature for my derived object was changed to this:
[DataContract]
[Init("derived object name!", "1001")]
public class DerivedObject : AbstractObject
{
}
Then, I created a method in my abstract class which is decorated with the OnDeserializing attribute, which will then gets called. Note that you have to create your method signature with a parameter of type StreamingContext. I also call the Init() in the ctor of the abstract class, for the case when the object is newed up through normal instantiation:
[DataContract]
[KnownType (typeof(DerivedObject))]
public abstract class AbstractObject
{
public AbstractObject()
{
Init();
}
[OnDeserializing]
private void Init(StreamingContext c)
{
Init();
}
private void Init()
{
Type t = this.GetType();
object[] customAttributes = t.GetCustomAttributes(false);
InitAttribute initAttribute = null;
foreach (object o in customAttributes)
{
if (o.GetType() == typeof(InitAttribute))
initAttribute = (InitAttribute)o;
}
if (initAttribute == null)
throw new Exception("Derived class must use InitAttritbute");
this.name = initAttribute.Name;
this.id = initAttribute.Id;
}
} Note the KnownType attribute, which is essential when creating derived classes from a base class that support serialization. In the Init() method, I rip out the values from the attribute, setting them on the object. With the above code, I can be sure that the object gets initialized correctly upon deserialization, as such:
static void ReadObject()
{
string json = string.Empty;
using (FileStream fs = new FileStream("json.txt", FileMode.Open))
{
using (StreamReader sr = new StreamReader(fs))
{
json = sr.ReadToEnd();
}
}
Stream s = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(json));
derivedObject = abstractObjectSerializer.ReadObject(s) as DerivedObject;
Console.WriteLine(derivedObject.Name);
Console.WriteLine(derivedObject.Id);
Console.WriteLine(derivedObject.Body);
Console.ReadLine();
}
Here’s the whole sample if you are interested.