Dynamic JSON Parsing in .NET with JsonValue

Posted by Rick Strahl on West-Wind See other posts from West-Wind or by Rick Strahl
Published on Mon, 19 Mar 2012 10:09:14 GMT Indexed on 2012/03/19 18:05 UTC
Read the original article Hit count: 2073

Filed under:
|
|

So System.Json has been around for a while in Silverlight, but it's relatively new for the desktop .NET framework and now moving into the lime-light with the pending release of ASP.NET Web API which is bringing a ton of attention to server side JSON usage. The JsonValue, JsonObject and JsonArray objects are going to be pretty useful for Web API applications as they allow you dynamically create and parse JSON values without explicit .NET types to serialize from or into. But even more so I think JsonValue et al. are going to be very useful when consuming JSON APIs from various services.

Yes I know C# is strongly typed, why in the world would you want to use dynamic values?

So many times I've needed to retrieve a small morsel of information from a large service JSON response and rather than having to map the entire type structure of what that service returns, JsonValue actually allows me to cherry pick and only work with the values I'm interested in, without having to explicitly create everything up front. With JavaScriptSerializer or DataContractJsonSerializer you always need to have a strong type to de-serialize JSON data into. Wouldn't it be nice if no explicit type was required and you could just parse the JSON directly using a very easy to use object syntax? That's exactly what JsonValue, JsonObject and JsonArray accomplish using a JSON parser and some sweet use of dynamic sauce to make it easy to access in code.

Creating JSON on the fly with JsonValue

Let's start with creating JSON on the fly. It's super easy to create a dynamic object structure. JsonValue uses the dynamic  keyword extensively to make it intuitive to create object structures and turn them into JSON via dynamic object syntax. Here's an example of creating a music album structure with child songs using JsonValue:

[TestMethod]
public void JsonValueOutputTest()
{
    // strong type instance 
    var jsonObject = new JsonObject();

    // dynamic expando instance you can add properties to
    dynamic album = jsonObject;

    album.AlbumName = "Dirty Deeds Done Dirt Cheap";
    album.Artist = "AC/DC";
    album.YearReleased = 1977;

    album.Songs = new JsonArray() as dynamic;
    
    dynamic song = new JsonObject();
    song.SongName = "Dirty Deeds Done Dirt Cheap";
    song.SongLength = "4:11";
    album.Songs.Add(song);

    song = new JsonObject();
    song.SongName = "Love at First Feel";
    song.SongLength = "3:10";
    album.Songs.Add(song);
    

    Console.WriteLine(album.ToString());
}

This produces proper JSON just as you would expect:

{"AlbumName":"Dirty Deeds Done Dirt Cheap","Artist":"AC\/DC","YearReleased":1977,"Songs":[{"SongName":"Dirty Deeds Done Dirt Cheap","SongLength":"4:11"},{"SongName":"Love at First Feel","SongLength":"3:10"}]}

The important thing about this code is that there's no explicitly type that is used for holding the values to serialize to JSON. I am essentially creating this value structure on the fly by adding properties and then serialize it to JSON. This means this code can be entirely driven at runtime without compile time restraints of structure for the JSON output.

Here I use JsonObject() to create a new object and immediately cast it to dynamic. JsonObject() is kind of similar in behavior to ExpandoObject in that it allows you to add properties by simply assigning to them. Internally, JsonValue/JsonObject these values are stored in pseudo collections of key value pairs that are exposed as properties through the DynamicObject functionality in .NET.

The syntax gets a little tedious only if you need to create child objects or arrays that have to be explicitly defined first. Other than that the syntax looks like normal object access sytnax. Always remember though these values are dynamic - which means no Intellisense and no compiler type checking. It's up to you to ensure that the values you create are accessed consistently and without typos in your code.

Note that you can also access the JsonValue instance directly and get access to the underlying type. This means you can assign properties by string, which can be useful for fully data driven JSON generation from other structures.

Below you can see both styles of access next to each other:

// strong type instance 
var jsonObject = new JsonObject();

// you can explicitly add values here
jsonObject.Add("Entered", DateTime.Now);

// expando style instance you can just 'use' properties
dynamic album = jsonObject;

album.AlbumName = "Dirty Deeds Done Dirt Cheap";

JsonValue internally stores properties keys and values in collections and you can iterate over them at runtime. You can also manipulate the collections if you need to to get the object structure to look exactly like you want. Again, if you've used ExpandoObject before JsonObject/Value are very similar in the behavior of the structure.

Reading JSON strings into JsonValue

The JsonValue structure supports importing JSON via the Parse() and Load() methods which can read JSON data from a string or various streams respectively. Essentially JsonValue includes the core JSON parsing to turn a JSON string into a collection of JsonValue objects that can be then referenced using familiar dynamic object syntax.

Here's a simple example:

[TestMethod]
public void JsonValueParsingTest()
{
    var jsonString = @"{""Name"":""Rick"",""Company"":""West Wind"",""Entered"":""2012-03-16T00:03:33.245-10:00""}";

    dynamic  json = JsonValue.Parse(jsonString);

    // values require casting
    string name = json.Name;
    string company = json.Company;
    DateTime entered = json.Entered;

    Assert.AreEqual(name, "Rick");
    Assert.AreEqual(company, "West Wind");            
}

The JSON string represents an object with three properties which is parsed into a JsonValue object and cast to dynamic. Once cast to dynamic I can then go ahead and access the object using familiar object syntax.

Note that the actual values - json.Name, json.Company, json.Entered - are actually of type JsonPrimitive and I have to assign them to their appropriate types first before I can do type comparisons. The dynamic properties will automatically cast to the right type expected as long as the compiler can resolve the type of the assignment or usage. The AreEqual() method oesn't as it expects two object instances and comparing json.Company to "West Wind" is comparing two different types (JsonPrimitive to String) which fails. So the intermediary assignment is required to make the test pass.

The JSON structure can be much more complex than this simple example. Here's another example of an array of albums serialized to JSON and then parsed through with JsonValue():

[TestMethod]
public void JsonArrayParsingTest()
{
  var jsonString = @"[
  {
    ""Id"": ""b3ec4e5c"",
    ""AlbumName"": ""Dirty Deeds Done Dirt Cheap"",
    ""Artist"": ""AC/DC"",
    ""YearReleased"": 1977,
    ""Entered"": ""2012-03-16T00:13:12.2810521-10:00"",
    ""AlbumImageUrl"": ""http://ecx.images-amazon.com/images/I/61kTaH-uZBL._AA115_.jpg"",
    ""AmazonUrl"": ""http://www.amazon.com/gp/product/B00008BXJ4/ref=as_li_ss_tl?ie=UTF8&tag=westwindtechn-20&linkCode=as2&camp=1789&creative=390957&creativeASIN=B00008BXJ4"",
    ""Songs"": [
      {
        ""AlbumId"": ""b3ec4e5c"",
        ""SongName"": ""Dirty Deeds Done Dirt Cheap"",
        ""SongLength"": ""4:11""
      },
      {
        ""AlbumId"": ""b3ec4e5c"",
        ""SongName"": ""Love at First Feel"",
        ""SongLength"": ""3:10""
      },
      {
        ""AlbumId"": ""b3ec4e5c"",
        ""SongName"": ""Big Balls"",
        ""SongLength"": ""2:38""
      }
    ]
  },
  {
    ""Id"": ""67280fb8"",
    ""AlbumName"": ""Echoes, Silence, Patience & Grace"",
    ""Artist"": ""Foo Fighters"",
    ""YearReleased"": 2007,
    ""Entered"": ""2012-03-16T00:13:12.2810521-10:00"",
    ""AlbumImageUrl"": ""http://ecx.images-amazon.com/images/I/41mtlesQPVL._SL500_AA280_.jpg"",
    ""AmazonUrl"": ""http://www.amazon.com/gp/product/B000UFAURI/ref=as_li_ss_tl?ie=UTF8&tag=westwindtechn-20&linkCode=as2&camp=1789&creative=390957&creativeASIN=B000UFAURI"",
    ""Songs"": [
      {
        ""AlbumId"": ""67280fb8"",
        ""SongName"": ""The Pretender"",
        ""SongLength"": ""4:29""
      },
      {
        ""AlbumId"": ""67280fb8"",
        ""SongName"": ""Let it Die"",
        ""SongLength"": ""4:05""
      },
      {
        ""AlbumId"": ""67280fb8"",
        ""SongName"": ""Erase/Replay"",
        ""SongLength"": ""4:13""
      }
    ]
  },
  {
    ""Id"": ""7b919432"",
    ""AlbumName"": ""End of the Silence"",
    ""Artist"": ""Henry Rollins Band"",
    ""YearReleased"": 1992,
    ""Entered"": ""2012-03-16T00:13:12.2800521-10:00"",
    ""AlbumImageUrl"": ""http://ecx.images-amazon.com/images/I/51FO3rb1tuL._SL160_AA160_.jpg"",
    ""AmazonUrl"": ""http://www.amazon.com/End-Silence-Rollins-Band/dp/B0000040OX/ref=sr_1_5?ie=UTF8&qid=1302232195&sr=8-5"",
    ""Songs"": [
      {
        ""AlbumId"": ""7b919432"",
        ""SongName"": ""Low Self Opinion"",
        ""SongLength"": ""5:24""
      },
      {
        ""AlbumId"": ""7b919432"",
        ""SongName"": ""Grip"",
        ""SongLength"": ""4:51""
      }
    ]
  }
]";

  dynamic albums = JsonValue.Parse(jsonString);
  
  foreach (dynamic album in albums)
  {
     Console.WriteLine(album.AlbumName + " (" + album.YearReleased.ToString() + ")");
     foreach (dynamic song in album.Songs)
     {
        Console.WriteLine("\t" + song.SongName  );
     }
  }

  Console.WriteLine(albums[0].AlbumName);
  Console.WriteLine(albums[0].Songs[1].SongName);
}

 

It's pretty sweet how easy it becomes to parse even complex JSON and then just run through the object using object syntax, yet without an explicit type in the mix. In fact it looks and feels a lot like if you were using JavaScript to parse through this data, doesn't it? And that's the point…

© Rick Strahl, West Wind Technologies, 2005-2012
Posted in .NET  Web Api  JSON  

© West-Wind or respective owner

Related posts about .NET

Related posts about Web Api