Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Dynamic JSON parser

4.89/5 (21 votes)
29 Aug 2012CPOL3 min read 120K   2.9K  
A simple JSON parser.

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:

C#
class Program
{
    static void Main(string[] args)
    {
        func(1); // 'int' does not contain a definition for 'error'
    }
    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_name
  • TrySetMember  - 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.

C#
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:

C#
dynamic dynamicConsoleWriter = new DynamicConsoleWriter();
dynamicConsoleWriter.First = "I am just a"; // TrySetMember is invoked
dynamicConsoleWriter.Last = " Lion!";       // TrySetMember is invoked 

var result1 = dynamicConsoleWriter + 2;     // TryBinaryOperation is invoked
var result2 = ++dynamicConsoleWriter;       // TryUnaryOperation is invoked
dynamicConsoleWriter[0] = "Hello";          // TrySetIndex is invoked
var result3 = dynamicConsoleWriter[0];      // TryGetIndex is invoked
var result4 = dynamicConsoleWriter.First;   // TryBinaryOperation is invoked
var result5 = dynamicConsoleWriter.Last;    // TryBinaryOperation is invoked 
var result6 = dynamicConsoleWriter.Count;   // DynamicConsoleWriter Count property is called

dynamicConsoleWriter.WriteFirst();          // TryInvokeMember is invoked
dynamicConsoleWriter.WriteLast();           // TryInvokeMember is invoked

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.

C#
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:

C#
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);           // John
Console.WriteLine(data.lastName);            // Smith
Console.WriteLine(data.age);                 // 25
Console.WriteLine(data.address.postalCode);  // 11229
Console.WriteLine(data.phoneNumber.Count);   // 2
Console.WriteLine(data.phoneNumber[0].type); // home
Console.WriteLine(data.phoneNumber[1].type); // fax
foreach (var pn in data.phoneNumber)
{
    Console.WriteLine(pn.number);            // 212 555-1234, 646 555-4567
}
Console.WriteLine(data.ToString());

// and creating JSON formatted data
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)