Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I serialize Dictionary<string, object> using DataContract serializer?

I'm planning to build a WCFservice that returns generic dictionary objects serialized to JSON. Unfortunately, serialization fails, as object is potentially always different. KnownTypes can't help, because property type is Dictionary, and I can't say KnownType, as the class will potentially be always different.

Any ideas if it's possible to serialize an 'unknown type'?

I don't mind to specify DataContract/DataMember for each of my classes, but (at least for prototype version) I don't want to have strong types for each and every response. Javascript client just doesn't care.

How about anonymous classes?

like image 717
tishma Avatar asked Apr 26 '12 11:04

tishma


2 Answers

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:

public class Person
{
    public string Name { get; set; }

    public int Age { get; set; }
}

And if I create an instance of Dictinoary<string, object> and add an instance of the Person class to it before serializing it:

var dictionary = new Dictionary<string, object>();
dictionary.Add("me", new Person { Name = "Yan", Age = 30 });
var serializer = new new JavaScriptSerializer();
var json = serializer .Serialize(dictionary);

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 if Person is subclassed without introducing any additional members:

public class Employee : Person
{
}

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 of Person but a Dictionary<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 that Dictionary<string, object> using the ConvertToType helper method:

var clone = serializer.Deserialize<Dictionary<string, object>>(json);
var personClone = serializer.ConverToType<Person>(clone["me"]);

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:

  1. Create a KnownTypeRegister class with static methods to add a type to the list of known types:

    public static class KnownTypeRegister
    {
        private static readonly ConcurrentBag _knownTypes = new ConcurrentBag();
        public static void Add(Type type)
        {
            _knownTypes.Add(type);
        }
        public static IEnumerable Get()
        {
            return _knownTypes.ToArray();
        }
    }
  2. Add a static constructor that registers the types with the register:

    [DataContract]
    public class Person
    {
        static Person()
        {
            KnownTypeRegister.Add(typeof(Person));
        }
        [DataMember]
        public string Name { get; set; }
        [DataMember]
        public int Age { get; set; }
    }
  3. 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!

like image 89
theburningmonk Avatar answered Oct 30 '22 13:10

theburningmonk


disclaimer I dont' recommend you expose object on a WCF endpoint; altho this appears 'flexible' this is not a good idea as you have not specified what sort of information will be served by your service.

and now for an answer

If your WCF call is being consumed by an ajax call and as you put it Javascript client just doesn't care. then why not make your WCF call simply return a string? Then the internals of your WCF call can serialize the Dictionary<string, object> using the JavaScriptSerializer

public string MyServiceMethod()
{
    var hash = new Dictionary<string, object>();
    hash.Add("key1", "val1");
    hash.Add("key2", new { Field1 = "field1", Field2 = 42});
    var serialized = new JavaScriptSerializer().Serialize(hash);
    return serialized;
}

disclaimer2 This is a means to the end (since you asked the question) - For a production quality application I would have a well defined interface so it was clear what was being requested and sent back over the wire.

like image 44
wal Avatar answered Oct 30 '22 13:10

wal