Introduction
There are many libraries to parse JSON formatted data,
so why would we want to create another one? Because .NET 4.0 Framework introduced a new type - dynamic
!
Background
dynamic
is actually a static type but compiler treats it differently than any other type. The compiler does not do any type
safety checks when it encounters a dynamic type (it bypasses static type checking).
For example:
class Program
{
static void Main(string[] args)
{
func(1);
}
static void func(dynamic obj)
{
obj.error = "oops";
}
}
The program above invokes func
with an argument of type int
and of course type int
does not have a property
named error
but the program will not generate any errors when we compile it. Things look different when we run the program. RuntimeBinderException
with
a message 'int' does not contain a definition for 'error' is thrown.
DynamicObject
The .NET layer that adds dynamic functionality is called Dynamic Language Runtime (DLR). DLR sits on top of
the Common Language Runtime (CLR).
Dynamic objects are represented by the IDynamicMetaObjectProvider
interface.
DynamicObject
is an abstract class that implements IDynamicMetaObjectProvider
and provides a set of basic operations that can be performed
on a dynamic object. A class that inherits from DynamicObject
can, for example, overwrite
the
TrySetMember
and TryGetMember
functions for setting and getting properties.
Here are the more important members of DynamicObject
that can be overridden to achieve a desired custom behavior of a dynamic object:
TryBinaryOperation
- binary operations *, +, - ...TryUnaryOperation
- unary operations --, ++, - ...TryGetIndex
- operations that access an object by index []TrySetIndex
- operations that set value by index [] TryGetMember
- get a property value for example, obj.property_nameTrySetMember
- set a property value for example, obj.property_name = "value"TryInvokeMember
- call a method for example, obj.SomeMethod(...)
Here is a sample implementation of all the methods listed above.
public class DynamicConsoleWriter : DynamicObject
{
protected string first = "";
protected string last = "";
public int Count
{
get
{
return 2;
}
}
public override bool TryBinaryOperation(BinaryOperationBinder binder,
object arg, out object result)
{
bool success = false;
if (binder.Operation == System.Linq.Expressions.ExpressionType.Add)
{
Console.WriteLine("I have to think about that");
success = true;
}
result = this;
return success;
}
public override bool TryUnaryOperation(UnaryOperationBinder binder, out object result)
{
bool success = false;
if (binder.Operation == System.Linq.Expressions.ExpressionType.Increment)
{
Console.WriteLine("I will do it later");
success = true;
}
result = this;
return success;
}
public override bool TryGetIndex(GetIndexBinder binder,
object[] indexes, out object result)
{
result = null;
if ( (int)indexes[0] == 0)
{
result = first;
}
else if ((int)indexes[0] == 1)
{
result = last;
}
return true;
}
public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
{
if ((int)indexes[0] == 0)
{
first = (string)value;
}
else if ((int)indexes[0] == 1)
{
last = (string)value;
}
return true;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
string name = binder.Name.ToLower();
bool success = false;
result = null;
if (name == "last")
{
result = last;
success = true;
}
else if (name == "first")
{
result = first;
success = true;
}
return success;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
string name = binder.Name.ToLower();
bool success = false;
if (name == "last")
{
last = (string)value;
success = true;
}
else if (name == "first")
{
first = (string)value;
success = true;
}
return success;
}
public override bool TryInvokeMember(InvokeMemberBinder binder,
object[] args, out object result)
{
string name = binder.Name.ToLower();
bool success = false;
result = true;
if (name == "writelast")
{
Console.WriteLine(last);
success = true;
}
else if (name == "writefirst")
{
Console.WriteLine(first);
success = true;
}
return success;
}
}
And here is how we use it:
dynamic dynamicConsoleWriter = new DynamicConsoleWriter();
dynamicConsoleWriter.First = "I am just a";
dynamicConsoleWriter.Last = " Lion!";
var result1 = dynamicConsoleWriter + 2;
var result2 = ++dynamicConsoleWriter;
dynamicConsoleWriter[0] = "Hello";
var result3 = dynamicConsoleWriter[0];
var result4 = dynamicConsoleWriter.First;
var result5 = dynamicConsoleWriter.Last;
var result6 = dynamicConsoleWriter.Count;
dynamicConsoleWriter.WriteFirst();
dynamicConsoleWriter.WriteLast();
Another cool feature of dynamic types is that they implement specificity, i.e., the most specific function call will be chosen at the runtime.
RuntimeBinderException
is thrown if the appropriate type is not found. The exception can be avoided by implementing a function that accepts an object.
public class Specificity
{
public static void printDynamic(dynamic obj)
{
print(obj);
}
protected static void print(List<int> list)
{
foreach (var item in list)
{
Console.WriteLine(item);
}
}
protected static void print(object obj)
{
Console.WriteLine("I do not know how to print you");
}
}
print(object obj)
is called if we pass anything but a List<int>
to
the printDynamic
function.
Dynamic JSON converter
JavaScriptSerializer will do the actual work of converting a JSON string into an
IDictionary<string, object>
.
JavaScriptSerializer
is located in the System.Web.Extensions assembly and using
System.Web.Script.Serialization
is required for the code to compile.
var serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new[] { new DynamicJsonConverter() });
dynamic data = serializer.Deserialize<object>(json);
serializer.Deserialize<object>(json)
parses the JSON string and calls
JavaScriptConverter
's Deserialize
method that we override to create a new
DynamicJsonObject
from a dictionary provided by the Deserialize
method.
DynamicObject
is the magic that converts a dictionary into a sexy object that has all JSON fields as properties.
ExpandoObject
is a new class that does exactly that but it will not work for us because we need more flexibility than it offers.
Each value in the deserialized dictionary is either a simple type (i.e., int
,
string
, double
, ...), IDictionary<string, object>
(i.e. {...}), or
ArrayList
.
We override DynamicObject
's TryGetMember
function to take care of all 3 types of values of the deserialized dictionary.
We will also throw in an implementation of TrySetMember
to allow to add new fields to our JSON object, and will also implement
IEnumerable
to allow easy iteration through dynamic JSON objects.
And here is how we use our dynamic parser:
const string json =
"{" +
" \"firstName\": \"John\"," +
" \"lastName\" : \"Smith\"," +
" \"age\" : 25," +
" \"address\" :" +
" {" +
" \"streetAddress\": \"21 2nd Street\"," +
" \"city\" : \"New York\"," +
" \"state\" : \"NY\"," +
" \"postalCode\" : \"11229\"" +
" }," +
" \"phoneNumber\":" +
" [" +
" {" +
" \"type\" : \"home\"," +
" \"number\": \"212 555-1234\"" +
" }," +
" {" +
" \"type\" : \"fax\"," +
" \"number\": \"646 555-4567\"" +
" }" +
" ]" +
" }";
var serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new[] { new DynamicJsonConverter() });
dynamic data = serializer.Deserialize<object>(json);
Console.WriteLine(data.firstName);
Console.WriteLine(data.lastName);
Console.WriteLine(data.age);
Console.WriteLine(data.address.postalCode);
Console.WriteLine(data.phoneNumber.Count);
Console.WriteLine(data.phoneNumber[0].type);
Console.WriteLine(data.phoneNumber[1].type);
foreach (var pn in data.phoneNumber)
{
Console.WriteLine(pn.number);
}
Console.WriteLine(data.ToString());
dynamic jdata = new DynamicJsonObject();
dynamic item1 = new DynamicJsonObject();
dynamic item2 = new DynamicJsonObject();
ArrayList items = new ArrayList();
item1.Name = "Drone";
item1.Price = 92000.3;
item2.Name = "Jet";
item2.Price = 19000000.99;
items.Add(item1);
items.Add(item2);
jdata.Date = "06/06/2004";
jdata.Items = items;
Console.WriteLine(jdata.ToString());
Acknowledgments
The initial dynamic JSON converter was written by Shawn Weisfeld.
History
- 03/21/2012: Updated source code. Use
JavaScriptSerializer
to convert DynamicJsonObject
to string. - 03/23/2012: Updated source code. Add
DynamicJsonObjectConverter
to properly serialize DynamicJsonObject
.