C-based JSON libraries
26 August 2019Recently I looked into what C-based JSON libraries were available that supported both reading and writing, because of the possibility that a prototype server I had written in Python might need to be ported to a more efficient language, and C is pretty much the only other language I would choose. This write-up of this investigation is to ensure I do not promptly forget that I found out.
What about XML?
In the early-2000s XML was one of those “hot” technologies that was more about marketing than technology, and was treated as a magic bullet for data interchange. The problem with XML is that it is designed for adding context and structure to predominantly text-based documents, with the goal of them still being human-readable in a bog-standard text editor, and not for transfer of data between applications. For transferring data it is unreasonably expensive, particularly when the data is lists of items:<list id="numbers"> <item>1</item> <item>2</item> <item>3</item> </list>
In comparison the JSON equivalent would be something like:
{ 'numbers' : [1,2,3] }
Not only more concise than XML, it is far less expensive to parse. I was once asked by my then-boss to evaluate an XML-based camera control protocol that was supposed to be a generalised replacement for the likes of Pelco-P and Pelco-D — needless to say I was not short of reasons to avoid it.
My use-case
I was recently implementing an application server using Django, though from the start I had thoughts about a more direct implementation using TCP/IP rather than having it as a web-service, in part for efficiency reasons. With that in mind is the question of what alternative languages I might use, which in practice meant would using C be viable. Aside from the use of JSON for the packet format using C for this sort of thing was not new for me, but since JSON is about 95% of the business-logic the practicality is entirely down to the JSON library. JSON in Python is a dream, but Java's JSONObject is something of a pain that makes me want to avoid Java for non-Android systems. For me a C JSON file needs both reading/writing support, but otherwise I can also get away with it having very limited functionality in order to aid efficiency.Finding JSON libraries
A short bit of searching led me to JSON.org which quite conveniently had a list of JSON libraries in various languages, and under C it listed 18 of them. In the space of about half an hour, typically based on a quick read of the description given on the respective websites, I had eliminated most of them as being unsuitable for what I would use them for. The roughly-chronological ordering of the reasons for elimination was as follows:- Parser only (10 libraries)
- I need a library that can both read and write JSON, and most of the libraries out there do just the latter. Yes some are extremely efficient at doing this, but JSON parsers are dime-a-dozen and I was looking for something much more general.
- Out of date (2 libraries)
- I don't want my own projects to depend on third-party code that was already abandoned years ago.
- Cross-language monstrosity (1 library)
- This library used one of those higher-level frameworks that generates bindings for multiple languages, and typical of such frameworks the lower-level code is horrendous. Using Maven for a C-based project? Forget it.
- Duplicate (1 library)
- Forked library. I was not going to examine both forks in detail.
- Not a proper encoder (1 library)
- Incidentally this was the other fork of the above. Listed as a JSON emitter it was really some extensions to printf to aid manual constriction of JSON output rather than a full-blown encoder.
Trying out the libraries
To see what they are like I wrote a small sample program that writes out a JSON object in each of the four libraries, and these are presented below alongside the corresponding output and some commentary — the order listed just happens to be the order I tried them out. On the whole I did not find much difference between the first three libraries, most likely because they all use a similar internal architecture, but WJElement takes a very different approach.Jansson
Jansson is available from the AlienBob Slackware repository, although at time of writing it provides v2.7 which was released October 2014 and omits some interesting functions such asjson_sprintf()
. There is no sample program for writing JSON, but jumping straight into the API reference very quickly tells you all that is required. It did not take me long to knock together an example for constructing a JSON response packet:
int main() { json_t *root = json_object(); json_t *list = json_array(); json_t *flags = json_array(); json_t *value; json_object_set_new(root, "type", json_string("reply")); json_object_set_new(root, "version", json_integer(2)); json_object_set_new(root, "flags", flags); json_array_append_new(flags, json_true()); json_array_append_new(flags, json_false()); json_array_append_new(flags, json_boolean(0)); json_array_append_new(flags, json_boolean(1)); value = json_integer(66); json_array_append(list, value); json_decref(value); // Explicit deref required json_array_append_new(list, json_integer(77)); json_array_append_new(list, json_null()); json_array_append_new(list, json_string("Remy")); json_array_append_new(list, json_stringn("Remy",4)); json_array_append_new(list, json_stringn_nocheck("Remy",4)); json_object_set_new(root, "list", list); char *out = json_dumps(root, JSON_ENSURE_ASCII|JSON_PRESERVE_ORDER|JSON_INDENT(3)); printf("JSON: %s\n", out); return 0; }
Jansson has its own reference counting system which is why there is both json_array_append()
and json_array_append_new()
— the latter is “reference stealing” so that unlike the former json_decref()
is not required. The above code will emit the following output, which is only indented due to use of the JSON_INDENT(3)
flag. Incidentally JSON_PRESERVE_ORDER
is default in later versions of Jansson.
JSON: { "type": "reply", "version": 2, "flags": [ true, false, false, true ], "list": [ 66, 77, null, "Remy", "Remy", "Remy" ] }
JSON-C
On the whole JSON-C's API is not much different from Jansson, aside from using different names for things, as can be seen in the code sample below. One gripe I do have is that some of the names are a little confusing, such as usingjson_object_get()
& json_object_put()
for incrementing/decrementing the reference counters — yes this does use nomenclature from the Linux kernel, but this does not mean it is good terminology, and in JSON-C get is also used for value lookup functions.
json_object *json = json_object_new_object(); json_object_object_add(json, "type", json_object_new_string("reply")); json_object *list = json_object_new_array(); json_object_array_add(list, json_object_new_int(1)); json_object_array_add(list, json_object_new_int(2)); json_object_array_add(list, json_object_new_int(3)); json_object_object_add(json, "list", list); printf("JSON: %s\n", json_object_to_json_string_ext(json, JSON_C_TO_STRING_PRETTY) ); return 0;
And the corresponding program output for the above code:
JSON: { "type":"reply", "list":[ 1, 2, 3 ] }
Parson
Person is a single source and header file that is supposed to be copied into your source code-base, which at the very least made it easy to “install”. It doesn't have much in terms of documentation beyond sample code on the library's web page, so writing the sample code shown below was a combination of guesswork and reading the header file. Having to use an init function and then use a get function that returns a separate pointer is somewhat annoying, although I suspect it was done to allow some type-based safety. On the pluys side the short-cut dot-notation is an interesting addition I have not seen in Jansson and JSON-C.int main() { JSON_Value *json = json_value_init_object(); JSON_Object *root = json_value_get_object(json); json_object_set_string(root, "type", "reply"); json_object_set_boolean(root, "ok", 0); json_object_dotset_string(root, "info.name", "Remy"); json_object_dotset_number(root, "info.id", 6); JSON_Value *list = json_value_init_array(); JSON_Array *array = json_value_get_array(list); json_array_append_string(array, "Str"); json_array_append_number(array,1); json_array_append_number(array,2); json_array_append_number(array,3); json_object_dotset_value(root, "info.list", list); printf("JSON: %s\n", json_serialize_to_string_pretty(json)); return 0; }
And as before, for completeness here is the corresponding program output:
JSON: { "type": "reply", "ok": false, "info": { "name": "Remy", "id": 6, "list": [ "Str", 1, 2, 3 ] } }
WJElement
WJElement implements a fancy selector system which I suspect is modelled on how Javascript does things and includes wild-card operators, and the implementation makes use of macros so the interface does not feel like a typical C-based API. I am not fond of interfaces that make heavy use of macro trickery, nor am I fond of the overheads of having to parse these selector strings, although I suspect in the grand scale of things the latter does not really matter. I originally looked at this library first and was going to give it a miss, but for completeness I created the small example below.int main() { WJElement root = WJEObject(NULL,NULL,WJE_NEW); WJEString(root, "type", WJE_SET, "reply"); WJEInt32(root, "code", WJE_SET, 6); WJEBool(root, "ok", WJE_SET, 1); WJEArray(root,"flags", WJE_SET); WJEBool(root, "flags[$]", WJE_SET, TRUE); WJEBool(root, "flags[$]", WJE_SET, TRUE); WJEBool(root, "flags[$]", WJE_SET, TRUE); WJEBool(root, "flags[1]", WJE_SET, FALSE); WJEDump(root); return 0; }
Assuming WJElement is installed into ${wje}
compiling & linking with gcc will need the -I${wje}/include
, -Wl,--rpath=${wje}/lib
, and -lwjelement
parameters. Once done the program will give the following output:
{ "type":"reply", "code":6, "ok":true, "flags":[ true, false, true ] }
Overall impressions
For me it is a choice between Jansson and JSON-C, as the differences are really just about taste and I have not yet taken the time to compare all their features. Jansson probably has more features, but I also suspect that JSON-C is more widely available. I feel that Parson's lack of documentation lets it down when in competition with the other two, but would give it a chance if the circumstances were right — projects that need to include all dependencies in-house spring to mind. Finally I feel that WJElement takes an approach that is over-complex for my use-case and deviates from how things would typically be done by a C programmer.