LINQ to JSON

One of the new features you'll see described (or mentioned) in various places about Silverlight 2 Beta 2 is "LINQ to JSON support". In my Vista Squad session last night I wanted to draw a comparison between DataContractJsonSerializer and LINQ to JSON (basically the two mechanisms you have for consuming JSON serialized data inside SL2) but was running short of time so had to leave elements as an exercise for the reader. Let me expand on things here...

I picked as my JSON data source, a query to the Yahoo image search service (I wanted a reasonably complex JSON object rather than just a simple "Person" class):

https://search.yahooapis.com/ImageSearchService/V1/imageSearch?appid=YahooDemo&query=VW%20Camper&output=json

This searches for images with the term "VW Camper" and returns the results in JSON format (remove &output=json from the end and you can see the results as XML). Cool. Of course, Silverlight prevents me making x-domain requests without a policy file in place and I didn't think I could just snap my fingers and get Yahoo to deploy a policy file so I had to workaround this. (TBH, relying on anything "off machine" for demos is generally to be avoided if at all possible so I'd probably have done this anyway). I created an ASP.NET handler that simply returns the same JSON response.

So I have a handler I can call to get a big lump of JSON but I'm not sure about what that lump of JSON looks like. More specifically I have no .NET class that maps to the JSON response, I'm just consuming a service from the cloud. Ho hum, what to do? I have a couple of choices (well probably more but for the purposes of this discussion, two choices). Which one I pick depends on how I want to use the data.

Imagine, for example, I wanted to query the service and get back the URLs for all the images which are jpegs. I might start off something like this:

     private void GetData()
    {
      Uri RequestUri =
        new Uri("https://localhost:56059/NetworkingDataWeb/Handler.ashx");
      HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(RequestUri);
      request.BeginGetResponse(new AsyncCallback(ResponseHandler), request);
    }

And my ResponseHandler might look something like this:

     private void ResponseHandler(IAsyncResult iar)
    {
      HttpWebRequest request = (HttpWebRequest)iar.AsyncState;
      HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(iar);

      using (Stream responseStream = response.GetResponseStream())
      {

        // Genius happens here, on Tuesdays only

      }
    }

Once I have my responseStream, I have a couple of choices; DataContractJsonSerializer or LINQ to JSON. Let's look at DataContractJsonSerializer first. In order to use DataContractJsonSerializer you need to define the .NET type that's going to be serialized / deserialized. We don't have that type currently so we're going to have to create it. Now I guess there's an easier way to do this but for me it involved a lot of head scratching and ultimately an iterative process serializing my .NET type, comparing the resulting JSON to the "real JSON" and refining it until I had something that matched. Perfectly. Given the "real JSON" looks a bit like this:

{"ResultSet":{"totalResultsAvailable":"45370","totalResultsReturned":10,"firstResultPosition":1,"Result":[{"Title":"vw_westfalia_camper_van.jpg","Summary":"We have earlier told you about some desirable, ultra-luxurious camper vans that are fitted with every luxury, you can't afford to live without. But, if you are a VW-Westfalia campers fan","Url":"http:\/\/www.bornrich.org\/images\/vw_westfalia_camper_van.jpg","ClickUrl":"http:\/\/www.bornrich.org\/images\/vw_westfalia_camper_van.jpg","RefererUrl":"http:\/\/www.bornrich.org\/entry\/the-all-new-vw-westfalia-camper-van","FileSize":16793,"FileFormat":"jpeg","Height":"291","Width":"444","Thumbnail":{"Url":"http:\/\/sp1.yt-thm-a01.yimg.com\/image\/25\/m1\/2160033383","Height":"91","Width":"140"}},{"Title":"mb75-G-23-VWCamper.jpg","Summary":"Click to enlarge\/reduce One of the earlier Superfast models, the 23 VW Camper carried on from the #34 series of VW vans.","Url":"http:\/\/www.recovertoy.com\/shop\/images\/boxes\/matchbox\/mb75-G-23-VWCamper.jpg","ClickUrl":"http:\/\/www.recovertoy.com\/shop\/images\/boxes\/matchbox\/mb75-G-23-VWCamper.jpg","RefererUrl":"http:\/\/www.recovertoy.com\/shop\/product_info.php?

(I've truncated at this point as it goes on like that for a while :-)) it's a non-trivial task. What I ended up with was a class that looked like this:

 public class ImageSearchResult
{
  public ImageResultSet ResultSet { get; set; }

  public class ImageResultSet
  {
    public int totalResultsAvailable { get; set; }
    public int totalResultsReturned { get; set; }
    public int firstResultPosition { get; set; }
    public ImageResult[] Result { get; set; }
  }

  public class ImageResult
  {
    public string Title { get; set; }
    public string Summary { get; set; }
    public string Url { get; set; }
    public string ClickUrl { get; set; }
    public string RefererUrl { get; set; }
    public string FileSize { get; set; }
    public string FileFormat { get; set; }
    public string Height { get; set; }
    public string Width { get; set; }
    public Thumbnail Thumbnail { get; set; }
  }

  public class Thumbnail
  {
    public string Url { get; set; }
    public string Height { get; set; }
    public string Width { get; set; }
  }
}

Now, armed with this class I can tackle the DataContractJsonSerializer. Add a reference to System.ServiceModel.Web then we have:

 using (Stream responseStream = response.GetResponseStream())
{

  DataContractJsonSerializer dcjs = new DataContractJsonSerializer(typeof(ImageSearchResult));

  ImageSearchResult isr = (ImageSearchResult)dcjs.ReadObject(responseStream);

  IEnumerable<string> query = from res in isr.ResultSet.Result
                              where res.FileFormat.ToLower() == "jpeg"
                              select res.Url;
}

and I get the URLs for the images in jpg format. And it looks very neat and tidy (and simple) because all the hard work was done up front in defining the .NET class.

LINQ to JSON on the other hand allows me to query the response without ever having to define that .NET class. Add a reference to System.Json then we have:

 using (Stream responseStream = response.GetResponseStream())
{

  JsonObject jo = (JsonObject)JsonObject.Load(responseStream);

  IEnumerable<string> query = from res in (JsonArray)jo["ResultSet"]["Result"]
                              let reso = res as JsonObject
                              where ((string)reso["FileFormat"]).ToLower() == "jpeg"
                              select (string)reso["Url"];
}

The LINQ to JSON query may not look as neat (due to the loosely typed nature of the JSON objects) but boy does this save me a lot of work. I don't have to define a .NET type that represents the full JSON object.

As I said, which technique you choose will likely depend on how you intend to use the data. If you're going to go on and do some manipulation then having the full .NET object will no doubt serve you well. If you're just doing a quick query to extract some data, LINQ to JSON could save you a lot of time.

Technorati Tags: silverlight,linq,json