Consuming JSON Services Without AJAX


JavaScript Object Notation (JSON) is one of the technologies used by AJAX and other "Web 2.0" technologies. It is a way to communicate in an object-oriented fashion between the web server and a client-side, JavaScript-based application. Every now and again, you might find yourself in a situation where you would need to consume a JSON service, but without a JavaScript endpoint. I ran into this situation when I needed to talk to an application that exposed a web interface, but has no other API hook. My mind first jumped to "screen scraping" the web pages in the exposed web interface. However, when I started to dive into the HTML, I found a very rich JSON service behind those pages.

An example message that I saw coming back from the application looked like this:

{"build":12639,"label": [
["done",8]
]
,"torrents": [

["74C61EB07A63ED2CBC84B8ECCFF85B1222A8006E",136,"myfile.iso",366779378],
["B28240047FD6C6A7219663AA862B2F1F4DD8AE24",136,"yourfile.iso",366784662]]
,"torrentc": "884739139"}

Examining the message, you'll notice that it seems to be broken into key-value pairs. For instance, the variable named "build" has a value of 12639. More complex object shapes can be represented by arrays like the value of the "label" variable. The above message could be represented in the C# class below:

[more]

class JsonTorrentList
{
  public int Build { get; set; }
  public List Labels { get; set; }
  public List Torrents { get; set; }
  public string TorrentCacheId { get; set; }
}

Of course, you could write a parsing function, to de-serialize the message into the object, but what I found was that something already exists (thankfully). The System.Runtime.Serialization and System.ServiceModel.Web assemblies / namespaces contain the DataContractJsonSerializer and the attributes you'll need to decorate your class with.

To use the DataContractJsonSerializer, you will need to add the DataContract and DataMember attributes to the class like so:

[DataContract()]
class JsonTorrentList
{
  [DataMember(Name = "build")]
  public int Build { get; set; }
  [DataMember(Name = "label")]
  public List Labels { get; set; }
  [DataMember(Name = "torrents")]
  public List Torrents { get; set; }
  [DataMember(Name = "torrentc")]
  public string TorrentCacheId { get; set; }
}

Notice that if you don't name your properties the same as the variable names in the JSON message, you'll need to supply the "Name" parameter of the DataMember attribute. Once that's done, you can write the de-serialization method like this:

static JsonTorrentList Parse(string jsonMessage)
{
  JsonTorrentList result = default(JsonTorrentList);
  MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(jsonMessage));
  DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(JsonTorrentList));
  result = (JsonTorrentList)ser.ReadObject(ms);
  ms.Close();
  return result;
}

Of course, the greatest advantage of using the built-in JSON de-serializer is that you don't have to write your own code to parse the message. In addition, the hardest part of consuming the service becomes matching its structure to a .NET object. There are some messages that just don't seem to make sense, but if you understand just a little bit about JSON, you'll be able to recognize the shape of the object you need to de-serialize to. Here are some things I have come to recognize as "hints" for this task:

  1. Square brackets "[" and "]" indicate the beginning and ending of an array.
  2. A colon ":" divides a variable name from its value
  3. A comma "," divides variables from each other and is also used to divide individual values within arrays

I learned a lot about JSON during this project. If you use the DataContractJsonSerializer, please, drop a comment and let me know how it works for you. Also, as a final note, I wanted to supply one more / different example to help get the idea across:

{"build":12639,"props": [{"hash": "74C61EB07A63ED2CBC84B8ECCFF85B1222A8006E"
,"trackers": "http://tracker.mytracker.com:6969/announce"
,"ulrate": 0
,"dlrate": 0
,"superseed": 0
,"dht": -1
,"pex": -1
,"seed_override": 0
,"seed_ratio": 2000
,"seed_time": 1800
,"ulslots": 0
}]
}

C#:

[DataContract()]
class JsonTorrentPropertyValues
{
  [DataMember()]
  public string hash { get; set; }
  [DataMember()]
  public string trackers { get; set; }
  [DataMember()]
  public int ulrate { get; set; }
  [DataMember()]
  public int dlrate { get; set; }
  [DataMember()]
  public int superseed { get; set; }
  [DataMember()]
  public int dht { get; set; }
  [DataMember()]
  public int pex { get; set; }
  [DataMember()]
  public int seed_override { get; set; }
  [DataMember()]
  public int seed_ratio { get; set; }
  [DataMember()]
  public int seed_time { get; set; }
  [DataMember()]
  public int ulslots { get; set; }
}

[DataContract()]
class JsonTorrentProperties
{
  [DataMember(Name = "build")]
  public int Build { get; set; }
  [DataMember(Name = "props")]
  public List Props { get; set; }
}