Well, if i had a gun near by, i’d definitely use that rather than the former 2.
So here’s the deal: i needed to send an object over the wire, and i decided on using Json. First pick: DataContractJsonSerializer. What i wanted to serialize is Filter definitions.
public class FilterDesc: IFilterDesc { public string PropertyName { get; set; } public FilterOp FilterOperation { get; set; } public object Value { get; set; } } public class CompositeFilterDesc: IFilterDesc { public List<IFilter> FilterDescriptors public FilterOp FilterOperation { get; set; } }
The way i decided on doing it is using a nice wrapper over DataContractJsonSerializer which would just take known types and do the serialization or deserialization. So basically this unit test should work
public void SerializationTest() { var cc = new FilterDesc { PropertyName="StartDate", Value=DateTime.Now } var cl2 = cc.SerializeJson().DeserializeJson<FilterDesc>(); Assert.AreEqual(cc.Value.GetType(), cl2.Value.GetType()); }
However, it fails miserably. It seems that DataContranctJsonSerializer has a problem with implicit conversion of types, even though the Date type is defined when sending over the wire. So no go there. There are some workarounds, but i was so pissed with the DCJS, because it’s not able to figure out its own serialization, that i went for the alternative: Json.NET.
Json.NET is able to figure out an implicit type, but it has it’s own share of issues. See that FilterDescriptors property in the CompositeFilterDesc? Well, with DCJS you would just feed it with “known types” and it would figure it out by itself. Json.NET doesn’t have that option. It has an option of sending full class names for every object over the wire, but then those objects have to have a same assembly name, and sometimes even be strong named, which is, well, unpractical. I needed something like this to work over the wire:
public void SerializationTest() { var cc = new CompositeFilterDesc { FilterDescriptors = new List<IFilterDesc> {new FilterDesc(),new CompositeFilterDesc()} }; var cl2 = cc.SerializeJson().DeserializeJson<CompositeFilterDesc>(new[]{typeof(FilterDesc),typeof(CompositeFilterDesc)}); Assert.AreEqual(cc.FilterDescriptors[1].GetType(), cl2.FilterDescriptors[1].GetType()); }
So i would provide the types like i would do with DCJS and it would just work.
To cut the long story short, i had to define a JsonConverter which would be used for overriding default Json.NET Deserialization. So here it is:
public class JsonKnownTypeConverter : JsonConverter { public IEnumerable<Type> KnownTypes { get; set; } public JsonKnownTypeConverter(IEnumerable<Type> knownTypes) { KnownTypes = knownTypes; } protected object Create(Type objectType, JObject jObject) { if (jObject["$type"] != null) { string typeName = jObject["$type"].ToString(); return Activator.CreateInstance(KnownTypes.First(x =>typeName.Contains("."+x.Name+","))); } throw new InvalidOperationException("No supported type"); } public override bool CanConvert(Type objectType) { if (KnownTypes == null) return false; return (objectType.IsInterface || objectType.IsAbstract) && KnownTypes.Any(objectType.IsAssignableFrom); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { // Load JObject from stream JObject jObject = JObject.Load(reader); // Create target object based on JObject var target = Create(objectType, jObject); // Populate the object properties serializer.Populate(jObject.CreateReader(), target); return target; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } }
And you would use it like this. These are the extension methods used in previous examples:
public static class AltiJsonSerializer { public static T DeserializeJson<T>(this string jsonString, IEnumerable<Type> knownTypes = null) { if (string.IsNullOrEmpty(jsonString)) return default(T); return JsonConvert.DeserializeObject<T>(jsonString, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto, Converters = new List<JsonConverter>( new JsonConverter[] { new JsonKnownTypeConverter(knownTypes) }) }); } public static string SerializeJson(this object objectToSerialize) { return JsonConvert.SerializeObject(objectToSerialize, Formatting.Indented, new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.Auto}); } }
Main work is in the deserializer, and you have to define types for objects sent over the wire (TypeNameHandling part). The type for deserialization is only determined through class name, but you can change the behaviour of the converter to suit your needs. HTH!
evs Aug 27 , 2012 at 3:46 am /
This is quite nice. I have adapted it a bit to suit my needs, the main thing I did was change the list of Known Types into a class, which is passed into the JsonKnownTypeConverter instead of a list of types. While doing this I’ve also ensured the each known type is tied to its base type.
public class KnownTypeConfiguration : Dictionary<Type, Dictionary>
{
public void Add(Type baseType, string key, Type knownType)
{
if (!baseType.IsAssignableFrom(knownType))
{
throw new InvalidCastException();
}
if(!this.ContainsKey(baseType))
{
this.Add(baseType, new Dictionary());
}
this[baseType][key] = knownType;
}
}