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

2

10

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?

Zoolatry answered 26/4, 2012 at 11:6 Comment(1)
I'm glad people like this already. Please don't bother to respond with arguments how WCF was not made for that etc, etc. I would appreciate if you can just let us know if there is a known way or if you have been there and gave up for valid reasons (what reasons?).Zoolatry
P
8

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!

Procreant answered 29/4, 2012 at 1:2 Comment(1)
First of all, thanks for the detailed response. It really looks like it's worth researching along the lines. I've also learned about ServiceStack on your blog, and it definitely sounds much more straightforward for building a REST JSON service than anything else.Zoolatry
J
1

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.

Jaddo answered 26/4, 2012 at 13:21 Comment(3)
Yes, returning a string is valid point. Having to serialize manually in each method is unacceptable though. I'm actually thinking of some kind of wrapper for that. I just felt that using DataContract serialization would be faster as it's part of framework. Is it?Zoolatry
However, Javascript code will have to exactly know what's returned from a request all the way from prototype to production, and I can't see how strict typing in .NET can help with that. Can it?Zoolatry
tishma, I dont have any metrics on whether DataContract would be faster. However it wouldnt surprise me if serializing using a third party JSON serializer (eg Json.NET) and returning a string could be faster than getting WCF to serialize. And no, strict typing won't help with the javascript side of things but with strict typing you can look at your 'public API' and see exactly what you expose as opposed to exposing a Dictionary<string, object> which returns who knows what.Jaddo

© 2022 - 2024 — McMap. All rights reserved.