All classes that derive from Node,
including Building, have the
[KnownType(typeof(type t))] attribute
applied to them.
KnownType
is usually applied to the base type - i.e.
[DataContract, KnownType(typeof(Building)), ...]
abstract class Node { ... }
(note - you can also specify the known-types in the DataContractSerializer
constructor, without requiring attributes)
EDIT RE YOUR REPLY
If the framwork class doesn't know about all the derived types, then you need to specify the known types when creating the serializer:
[DataContract] abstract class SomeBase { }
[DataContract] class Foo : SomeBase { }
[DataContract] class Bar : SomeBase { }
...
// here the knownTypes argument is important
new DataContractSerializer(typeof(SomeBase),
new Type[] { typeof(Foo), typeof(Bar) });
This can be combined with (for example) preserveObjectReferences
etc by replacing the null
in the previous example.
END EDIT
However, without something reproducible (i.e. Node
and Building
), it is going to be hard to help much.
The other odd thing; trees structures are very well suited to things like DataContractSerializer
- there is usually no need to flatten them first, since trees can be trivially expressed in xml. Do you really need to flatten it?
Example:
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Xml;
[DataContract, KnownType(typeof(Building))]
abstract class Node {
[DataMember]
public int Foo {get;set;}
}
[DataContract]
class Building : Node {
[DataMember]
public string Bar {get;set;}
}
static class Program
{
static void Main()
{
Dictionary<Guid, Node> data = new Dictionary<Guid, Node>();
Type type = typeof(Dictionary<Guid, Node>);
data.Add(Guid.NewGuid(), new Building { Foo = 1, Bar = "a" });
StringWriter sw = new StringWriter();
using (XmlWriter xw = XmlWriter.Create(sw))
{
DataContractSerializer dcs = new DataContractSerializer(type);
dcs.WriteObject(xw, data);
}
string xml = sw.ToString();
StringReader sr = new StringReader(xml);
using (XmlReader xr = XmlReader.Create(sr))
{
DataContractSerializer dcs = new DataContractSerializer(type);
Dictionary<Guid, Node> clone = (Dictionary<Guid, Node>)
dcs.ReadObject(xr);
foreach (KeyValuePair<Guid, Node> pair in clone)
{
Console.WriteLine(pair.Key + ": " + pair.Value.Foo + "/" +
((Building)pair.Value).Bar);
}
}
}
}
Best Answer
NOTE: I've gone into a lot of details about JavaScriptSerializer at the start of my answer, if you just want to read about the resolution to the known type problem mentioned in the original question, jump to the end of the answer.
Performance
Based on the benchmarks I ran, the JavaScriptSerializer is the far slower than the other alternatives and can take 2x as long to serialize/deserialize an object compared to DataContractSerializer.
No need for Known Type
That said, the JavascriptSerializer is more flexible in that it doesn't require you to specify 'known types' ahead of time, and the serialized JSON is cleaner at least in the case of dictionaries (see examples here).
The flip side of that flexibility around known types is that it won't be able to deserialize that same JSON string back to the original type. For instance, suppose I have a simple
Person
class:And if I create an instance of
Dictinoary<string, object>
and add an instance of thePerson
class to it before serializing it:I'll get the following JSON
{"me":{"Name":"Yan","Age":30}}
which is very clean but devoid of any type information. So supposed if you have two classes with the same member definitions or ifPerson
is subclassed without introducing any additional members:then there's simply no way for the serializer to be able to guarantee that the JSON
{"Name":"Yan","Age":30}
can be deserialized to the correct type.If you deserialize
{"me":{"Name":"Yan","Age":30}}
using the JavaScriptSerializer, in the dictionary you get back the value associated with "me" is NOT an instance ofPerson
but aDictionary<string, object>
instead, a simple property bag.If you want to get a
Person
instance back, you could (though you most probably would never want to!) convert thatDictionary<string, object>
using theConvertToType
helper method:On the other hand, if you don't need to worry about deserializing those JSON into the correct type and JSON serailization is not a performance bottleneck (profile your code and find out how much CPU time's spent on serialization if you haven't done so already) then I'd say just use JavaScriptSerializer.
Injecting Known Type
IF, at the end of the day, you do still need to use DataContractSerializer and need to inject those KnownTypes, here are two things you can try.
1) Pass the array of known types to the DataContractSerializer constructor.
2) Pass a subclass of DataContractResolver (with the means to locate the types of interest to you) to the DataContractSerializer constructor
You can create a 'known type registry' of sorts that keeps track of the types that can be added to the dictionary, and if you control all the types that you'll need to inject to the DataContractSerializer, you can try the simplest thing:
Create a
KnownTypeRegister
class with static methods to add a type to the list of known types:Add a static constructor that registers the types with the register:
Get the array of known types from the register when you construct the serializer:
var serializer = new DataContractSerializer(typeof(Dictionary<string, object>), KnownTypeRegister.Get());
More dynamic/better options are possible but they're also more difficult to implement, if you want to read more about dynamic known type resolution, have a look at Juval Lowy's MSDN article on the topic here. Also, this blog post by Carlos Figueira also goes into details on more advance techniques such as dynamically generating the types, well worth a read whilst you're on the topic!