Lisp Web Tips

This is a random collection of tips that may help you when developing JSON-based web services with Lisp.

Use explicit JSON encoding in CL-JSON

CL-JSON, like other encoder/decoder tools for Lisp and other languages, has a standard set of rules for mapping from JSON objects to Lisp objects, and back. The atomic cases, like numbers and strings are easy, as are arrays. But there's a problem when going from Lisp lists to JSON. Should a list like

((a 1) (b 2) (c 3))

be mapped to an array of pairs or a JSON object with the keys A, B and C? CL-JSON's table say that lists go to arrays, except for association lists. The documentation doesn't say how it decides when a list is an association list. If you look at the code, you'll see that it tests the first pair to see if the CDR is an atom. The above list is NOT an association list, by that rule.

CL-JSON has functions like encode-json-alist that can force a list to be treated as an association list but that doesn't help on nested lists.

So if you're writing code to construct nested data to be translated to JSON, what can you do? One alternative is to use the streaming encoder functions. But that makes for code that's harder to unit test.

Fortunately, study of the source code shows that there is an undocumented but exported macro with-explicit-encoder that lets you mark each list for how you want it treated. You use it like this:

(with-explicit-encoder (encode-json-to-string ...))

You mark the front of every list, to specify how you want it encoded. Put :array to map a list to an array, :alist to map a list of pairs to a JSON object, and :plist to map a plist to a JSON object. For example:

(:plist :test "exam-1"
  :scores (:array (:plist :name "John" :score 85)
                  (:plist :name "Mary" :score 92)))

Check for JSON failure

You don't know when, if ever, your AJAX call will hear back from the server. Therefore, AJAX calls are best done with asynchronous callback functions. For example, using the popular jQuery library, a simple call to get some data could be written in several equivalent ways:

$.getJSON("/results", function (response) {...});
$.getJSON("/results", { 
  success: function (response) {...}
 .done(function (response) {...});

All of these call the function with the response data whenever data is returned to the browser by the server. The getJSON returns immediately so that the browser is not frozen, waiting for that response. (That means that you must put code that uses the response inside the callback, not after the getJSON, but you knew that, right?)

All of the above forms will work but are bad ideas, because if there's something broken in the response, e.g., bad JSON data, you will not get any indication that a problem occurred.

Always define a failure handler. E.g., one of the following

$.getJSON("/results", { 
  success: function (response) {...}, 
  error: function () {...}
 .done(function (response) {...})
 .fail(function () { ...});
Faculty: Chris Riesbeck
Time: Monday, Wednesday, Friday: 1pm - 2pm
Location:Annenberg G15


Important Links