Click here to Skip to main content
15,867,488 members
Articles / Programming Languages / C#

Dynamic JSON parser

Rate me:
Please Sign up or sign in to vote.
4.89/5 (21 votes)
29 Aug 2012CPOL3 min read 116.2K   2.9K   52   22
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)


Written By
Engineer @ Curbsidr
United States United States
Check our technical blog for more tips and articles @ https://curbsidr.com/blog/

Comments and Discussions

 
Questionnice Pin
Parsons Nash8-Mar-23 11:08
Parsons Nash8-Mar-23 11:08 
QuestionThanks! Pin
Odobasa2wyomingps.org 1861528110-Nov-22 13:11
Odobasa2wyomingps.org 1861528110-Nov-22 13:11 
Questiongood! Pin
mcdonald lowery10-Nov-22 13:06
mcdonald lowery10-Nov-22 13:06 
Questiongood Pin
mcdonald lowery10-Nov-22 12:57
mcdonald lowery10-Nov-22 12:57 
QuestionKeep comments Pin
Member 155275777-Feb-22 22:53
Member 155275777-Feb-22 22:53 
PraiseReally appreciate for providing this code Pin
biplov24-Dec-20 2:34
biplov24-Dec-20 2:34 
QuestionCool Pin
carriedean23-Jul-20 21:59
carriedean23-Jul-20 21:59 
QuestionQuestion Pin
Member 1066951114-Feb-15 10:52
Member 1066951114-Feb-15 10:52 
QuestionPerformance Pin
FatCatProgrammer26-Dec-14 6:46
FatCatProgrammer26-Dec-14 6:46 
Questionhow to Implemente DynamicJsonConverter.Serialize ? Pin
Atwind27-Apr-14 20:17
Atwind27-Apr-14 20:17 
QuestionJSON Value or Newtonsoft JSON? Pin
Garry Lowther19-Aug-13 22:45
Garry Lowther19-Aug-13 22:45 
AnswerRe: JSON Value or Newtonsoft JSON? Pin
Athari5-Sep-13 5:48
Athari5-Sep-13 5:48 
GeneralMy vote of 5 Pin
computergurubd22-May-13 4:22
computergurubd22-May-13 4:22 
QuestionА может так? Pin
Member 987154727-Feb-13 22:32
Member 987154727-Feb-13 22:32 
GeneralMy vote of 5 Pin
Member 85635782-Dec-12 16:54
Member 85635782-Dec-12 16:54 
GeneralMy vote of 5 Pin
Christian Amado29-Aug-12 17:38
professionalChristian Amado29-Aug-12 17:38 
QuestionWhy Pin
xComaWhitex29-Aug-12 13:41
xComaWhitex29-Aug-12 13:41 
QuestionI get an error Pin
rahiparikh5-Jun-12 8:41
rahiparikh5-Jun-12 8:41 
AnswerRe: I get an error Pin
Sei Flavius28-Jun-12 12:33
Sei Flavius28-Jun-12 12:33 
QuestionCool Pin
Sacha Barber20-Mar-12 3:33
Sacha Barber20-Mar-12 3:33 
AnswerRe: Cool Pin
Sei Flavius20-Mar-12 6:39
Sei Flavius20-Mar-12 6:39 
GeneralRe: Cool Pin
Sacha Barber21-Mar-12 22:34
Sacha Barber21-Mar-12 22:34 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.