In the process of working on a game in Silverlight 2.0 I had a need to fetch some data from Google and manipulate it in C#. The premise of the game will be the subject of a future post. For now I want to talk about deserializing the json data returned by Google’s AJAX (RESTful) API using System.Runtime.Serialization.Json.DataContractJsonSerializer (implemented in System.ServiceModel.Web.dll).
The json serializer is a new addition in the .NET framework 3.5, and although there are a number of examples of how to use this class available on the web, all of them work with very simple, flat json objects. The json objects returned by Google’s API are not that simple. There are a number of nested types that make it a somewhat more complicated scenario than I have seen discussed on other sites. You can read about the AJAX API and the json result format here.
Specifically the examples given for Silverlight show what I will call “simple json deserialization.” Using this approach you can just declare a public class with all public properties representing the json attributes, and use this with the DataContractJsonSerializer. There is no need for [DataContract] or [DataMember] attributions. However, this approach failed with the more complex Google result format. While the ReadObject() call completed successfully all my properties were null. After messing around with it this afternoon while the kids played with their presents I finally came up with something that works. The relevant code is presented below:
[DataContract(Name="pages")] public class GPage { [DataMember(Name = "start")] public string Start { get; set; } [DataMember(Name = "label")] public string Label { get; set; } } [DataContract(Name="cursor")] public class GCursor { [DataMember(Name = "pages")] public GPage[] Pages { get; set; } [DataMember(Name = "estimatedResultCount")] public string EstimatedResultCount { get; set; } [DataMember(Name = "currentPageIndex")] public string CurrentPageIndex { get; set; } [DataMember(Name = "moreResultsUrl")] public string MoreResultsUrl { get; set; } } [DataContract(Name = "results")] public class GResults { [DataMember(Name = "GsearchResultClass")] public string GSearchResultClass { get; set; } [DataMember(Name = "html")] public string Html { get; set; } } [DataContract(Name="GimageResult")] public class GImageResults : GResults { [DataMember(Name="title")] public string Title { get; set; } [DataMember(Name = "titleNoFormatting")] public string TitleNoFormatting { get; set; } [DataMember(Name = "unescapedUrl")] public string UnescapedUrl { get; set; } [DataMember(Name = "url")] public string Url { get; set; } [DataMember(Name = "visibleUrl")] public string VisibleUrl { get; set; } [DataMember(Name = "originalContextUrl")] public string OriginalContextUrl { get; set; } [DataMember(Name = "width")] public int Width { get; set; } [DataMember(Name = "height")] public int Height { get; set; } [DataMember(Name = "tbWidth")] public int TbWidth { get; set; } [DataMember(Name = "tbHeight")] public int TbHeight { get; set; } [DataMember(Name = "tbUrl")] public string TbUrl { get; set; } [DataMember(Name = "content")] public string Content { get; set; } [DataMember(Name = "contentNoFormatting")] public string ContentNoFormatting { get; set; } } [DataContract(Name="responseData")] public class GImageResponseData { [DataMember(Name="results")] public GImageResults[] Results { get; set; } [DataMember(Name="cursor")] public GCursor Cursor { get; set; } } [DataContract(Name="GImageResponse")] public class GImageResponse { [DataMember(Name="responseData")] public GImageResponseData Response { get; set; } [DataMember(Name="responseDetails")] public string ResponseDetails { get; set; } [DataMember(Name="responseStatus")] public string ResponseStatus { get; set; } } // omitted: WebClient call to retrieve result stream private void BuildUriList( Stream results ) { DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(typeof(GImageResponse)); GImageResponse gir = null; try { gir = (GImageResponse)jsonSerializer.ReadObject(results); } catch( Exception ex ) { throw new Exception("Error deserializing json data", ex); } results.Close(); foreach(GImageResults g in gir.Response.Results) { Uri u = new Uri(g.TbUrl); _uriList.Add(u); } }
The only code I have omitted here is the WebClient call that actually retrieves the result stream. The key point of this is that in order to get everything working I had to fully attribute the classes and data members. The breakthrough came when I attributed one of the classes and noticed that suddenly the properties implemented in that class were correctly deserialized, while the rest remained null. The “simple” approach just didn’t work with nested types. You’ll note that the hierarchy of classes represented in the listing fully implements the Google json result set. In this case the search I am conducting is for images, however I plan to pull this code out into a library that is more generally useful for manipulating Google search results in C#.
In the meantime, now that I am past this Christmas roadblock, on with the game (which is, after all, the fun part).