问题
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?
回答1:
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:
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(); } }
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; } }
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!
回答2:
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.
来源:https://stackoverflow.com/questions/10332158/can-i-serialize-dictionarystring-object-using-datacontract-serializer