Deserializing to Persistent AnonymousTypes with JSON.Net October 9, 2010
Posted by ActiveEngine Sensei in .Net, .Net Development, ActiveEngine, C#, Problem Solving.Tags: ActiveEngine, AnonymousType, C#, JSON.Net
trackback
A few weeks back Sensei unleashed a crazy idea regarding a class AnonymousType that could persist values from an anonymous object. The AnonymousType, created by Hugo Benocci models an individual object. In a sense this is a hyper-charged Dictionary of properties that represent an object. It’s meta data. This is similar to a concept called the Adaptive Object Model, the theory that you create mechanisms to describe what your objects should do. Instead of having a class for SalesAgent or Car you have classes that represent the classes, attributes, relationships and behavior in your domain. In other words, you create a meta data modeler and feed it the criteria that would represent SalesAgent, Car, etc.
Having a “sound-of-one-hand-clapping” moment, Sensei realized that while “Persistent AnonymousTypes” was in the title of the post, no mechanism for for serializing the AnonymousType was included!! “What the …”. Jeeezz! “Hell, that should be easy”, Sensei says. Grab JSON.Net and with elbow grease make it work, right? Anybody?
One thing that should be immediately clear is that all the meta data is locked up in the AnonymousType object, so you can’t just write:
string json = JsonConvert.SerializeObject(anonymousType);
Instead we need a way represent all the properties of our AnonymousType and preserve each property’s name, it’s type, and the underlying value. Something like:
public class NameTypeValue { public string Name { get; set; } public Type Type{get; set;} public object Value { get; set; } }
And wouldn’t it be nice if we could take a serialized stream of an actual object and convert that into an AnonymousType? Thinking further ahead, it would rather easy to pass around a list of NameTypeValues as you could easily send and receive this object from a web client or other front end, building yourself a modelling or code generation tool.
Serializing the object depicted above is pretty trivial. Using a Func<Dictionary<string,object>, string, string> we can serialize any way wish as with two tiny methods:
public string ToJSON(Func, string, string> function, string jsonObjectName) { return function(_Values, jsonObjectName); } /// Method to serialize. You can come up with your own!! public string SerializeWithJObject(Dictionary values, string name) { var jsonObject = new JObject(); foreach (KeyValuePair property in values) { jsonObject.Add(new JProperty(property.Key, property.Value)); } return jsonObject.ToString(); }
If there is another mechanism for serialization that you wish to use you are free to come up with your own. For illustration here is the JSON output of an AnonymousType for a sales agent, and followed by the JSON for an actual Agent object:
Agent JSON ==>{“Name”:”Sales Guy Rudy”,”Department”:45}
AnonymousType JSON ==>{ “Name”: “Sales Guy Rudy”, “Department”: 45}
Now that we can simply serialize our AnonymousType with the output matching that of an actual object, we just need a way to interpret a JSON stream and build an AnonymousType. Along with discussion, Sensei will talk about the second “sound-of-one-hand-clapping” moment he had when working with JSON.Net. As you may have already surmised, you need to describe the Type of property in order deserialization to happen. Sensei didn’t and took a trip to the valley for frustration.
Ok. We have stream of JSON with the Name, Value and Type of each property for an object. AnonymousType has a Set method to set a new property:
/// <summary> /// Sets the value of a property on an anonymous type /// </summary> /// <remarks>Anonymous types are read-only - this saves a value to another location</remarks> public void Set(string property, object value) { this.Set<object>(property, value); } /// <summary> /// Sets the value of a property on an anonymous type /// </summary> /// <remarks>Anonymous types are read-only - this saves a value to another location</remarks> public void Set<T>(string property, T value) { //check for the value if (!this.Has(property)) { this._Values.Add(property, value); } else { //try and return the value try { this._Values[property] = value; } catch (Exception ex) { throw new Exception( string.Format( AnonymousType.EXCEPTION_COULD_NOT_ACCESS_PROPERTY, property, (value == null ? "null" : value.GetType().Name), ex.Message ), ex); } } }
It’s pretty straight forward to accept a NameTypeValue object and perform:
public void AddProperty(string objectName, NameTypeValue nameTypeValue) { // Object doesn't exist? Add. if (objects.ContainsKey(objectName) == false) { objects.Add(objectName, new List()); } var properties = objects[objectName]; // All properties are unique var existingProperty = properties.Where(x => x.Name == nameTypeValue.Name) .SingleOrDefault(); if(existingProperty == null) { properties.Add(nameTypeValue); } }
and taking this a step further, a List<NameTypeValue> can supply all properties for an object:
properties.ForEach(x => { anonymousType.Set(x.Name, x.Value); });
Accepting a JSON stream of a List<NameTypeValue> should be easy-cheesey mac-n-peasey. The first version of this looked like the following:
public AnonymousType DeserializeFromJSONProperties(string objectName, string json) { Enforce.ArgumentNotNull(objectName, "AnonFactory.Deserialize - objectName can not be null"); Enforce.ArgumentNotNull(json, "AnonFactory.Deserialize - json can not be null"); List propertyList = JsonConvert.DeserializeObject >(json); // Add properties. Make sure int is not deserialized to a long since JSON.Net // makes best guess propertyList.ForEach(x => AddProperty(objectName, x)); return CreateAnonymousType(objectName); }
But one-moooorrree-thing! Sensei discovered that JSON.Net, when presented with an integer like 5, will deserialize to the largest possible type when not presented with a target. In other words, when you have this JSON:
{“Department” : 45}
and deserialize to an object, it must accommodate the largest possible type in order to avoid truncating the data. That means an int is deserialized as Int64!! The first round of testing was quite aggravating as AnonymousType would accept the property into it’s schema, but when you went to fetch that value later on you would get an exception In other words, when you did this:
// Found in JSONTests.MakeItFail() var anonFactory = new AnonFactory(); var darrellDept = new NameTypeValue(); darrellDept.Name = "Department"; darrellDept.Value = 45; var darrellName = new NameTypeValue(); darrellName.Name = "Name"; darrellName.Value = "Darrell"; var propertyList = new List(); propertyList.Add(darrellDept); propertyList.Add(darrellName); // Create JSON stream of properties string darrellPropertyJSON = JsonConvert.SerializeObject(propertyList); // Try to deserialize and create an AnonymousType object var otherDarrell = anonFactory.DeserializeFromJSONProperties("Agent", darrellPropertyJSON); Assert.AreEqual(otherDarrell.Get("Department"), 45);
you got an InvalidCastException.
Luckily you have the Type so you can perform a conversion as you deserialize the property and add it to AnonymousType’s Dictionary<string, object>. Here’s the new version:
propertyList.ForEach(x => AddProperty(objectName, ConvertTypeFromDefinition(x))); private NameTypeValue ConvertTypeFromDefinition(NameTypeValue nameTypeValue) { if (nameTypeValue.Type != nameTypeValue.Value.GetType()) { nameTypeValue.Value = Convert.ChangeType(nameTypeValue.Value, nameTypeValue.Type); } return nameTypeValue; }
When you look at the new version of the AnonymoustType project you’ll note that serializing is handled by the AnonymousType itself, while a factory class is used for building the an AnonymousType from the NameTypeValue’s and for deserializing JSON as well. Sensei struggled a bit with this, as on the one hand if AnonymousType was responsible for serializing itself should it also be able to deserialize a stream? On the other hand, a factory seemed logical since you could have a registry of AnonymousType objects, thereby centralizing the creation and management of AnonymousTypes. Don’t like it – create your own and share! Regardless, looks like we can fit through the mini-Stargate now. Here’s version 2.
[…] aware, anonymous types in C# have a limited scope, but the Persistent Anonymous type that Sensei discussed allows you to create a structure that mimics a standard anonymous type while being persist that […]