September 10, 2013

Ada with a side of JSON

So, you've got Ada and you need to handle some JSON data, but you're not quite sure if there are tools available, or if you're going to have to come up with a homegrown solution. Well, as luck will have it, you need look no further than to the excellent GNATColl library from libre.adacore.com:

The GNAT Component Collection is a suite of reusable software components and utilities. It has been used by AdaCore in developing the GNAT tool set, the GPS Integrated Development Environment, and GNAT Tracker, its web-based customer support interface.

There's a lot of really nice stuff in GNATColl, but today I'll focus on the very new and shiny JSON features. Actually, at the time of writing, the GNATColl JSON facilities are so new that they haven't even made it into the GNATColl manual yet, but that's not really a problem, since they are so very easy to grasp.

But before we can get cracking on the actual Ada code, we need to install GNATColl. My personal preference is to go with the latest development snapshot:



Obviously you should take a look at the configure options, so you don't end up trying to compile features you don't want/need. Also it's worth noting that if you want to be sure of success, then it's probably best to use the GNAT GPL compiler from AdaCore. Once you've made it work with that, you can start experimenting with the FSF GCC compiler. I've compiled GNATColl with GCC 4.6.2, so I can at least attest to the fact that GNATColl compiled with that specific version at the time of writing.

So, now that we have GNATColl available, lets get down and dirty with some JSON.

Step one is to initialize an empty JSON_Value variable:



Running that should give you this output:

Yes, Penguin is a JSON object

The Create_Object call is where the magic is at. This gives us an empty JSON object, to which we can add values using the Set_Field procedure. The Kind function returns the kind of JSON_Value we're dealing with. There are 7 kinds:



It should be obvious what kinds of data the different types contain.

So, currently we've got an empty Penguin JSON object, next step is naming the cute little fellow. Add this to the JSON_Fun program:



And voila! We've got a penguin named Linux. Amazing stuff eh? In the above snippet we encounter two key subprograms in the GNATCOLL.JSON package: Set_Field and Get. The former adds data to a JSON_Value object, while the latter retrieves data. There are Set_Field and Get subprograms for all the available JSON_Value_Type's.

Moving on, lets give our penguin some parents. As we all know, Linux got a lot of parents, but we'll settle on adding three of those to a JSON_Array:



Woah! Lots of new stuff going on here. Lets take it from the top. First we declare a new JSON_Array object: Parents. We then append JSON_Value objects to Parents using the Append procedure, which takes a JSON_Array as its first parameter and a JSON_Value as its second. If you've programmed for more than 2 weeks, you should already have guessed that the value of the second parameter is appended to the JSON_Array given as the first parameter. There's an alternative method: Using the "&" function. It's slower, but to some the code is more readable:



I personally like the Append approach, but you can use whatever floats your boat. Naturally you can append both JSON_Value's and JSON_Array's.

Next we have the Create function. There's a series of Create functions in the GNATCOLL.JSON package, each returning a JSON_Value containing the data given in its sole parameter. In our case we give Create a String, so it returns a JSON_Value where the JSON_Value_Type is JSON_String_Type.

The Length (Parents) call returns the amount (Natural) of items in the Parents array, and in the loop we make use of that number to set the range. It would've been nice to not have to define the lower bound of the range with an actual number. I would've much preferred something like Parents'Range, but hey, you can't have it all.

In the loop we stumble on a less than pretty construction:



Gaggle! Lots of Get'ing going on there. But before you tear your hair out in frustration, lets take a look at the specification for the two Get functions used here:



Aha! Suddenly everything makes sense again. One could argue that readability could've been improved slighty, had the innermost call been named differently, but since that's not the case, we're just going to have to live with Get'ing twice.

Finally we output the JSON we've created:



Executing the program results in this:

Yes, Penguin is a JSON object
Our Penguin is named Linux
Linux got 3 parents.
They are:
Linus Torvalds
Alan Cox
Greg Kroah-Hartman
{"parents":["Linus Torvalds", "Alan Cox", "Greg Kroah-Hartman"], "name":"Linux"}

Amazing! Now, just as there's a Write function for turning a JSON object into a String, there's also a Read function to turn a JSON String into a JSON_Value object:



We Read the JSON String generated by the Write (Penguin) call into our newly declared Pingu object. The Filename parameter gives a file to where error messages are written, in case the given JSON String is mangled in some way. The two Set_Field calls overwrite the name and parents fields with new values (another famous penguin!), and finally we output the new JSON String, adding this to the previous output:

{"parents":"Otmar Gutmann", "name":"Pingu"}

Neat eh?

Using the Map_JSON_Object procedure it is also possible to iterate a JSON_Value object:



So as you can see, you can do most everything with the tools available in GNATCOLL.JSON - there is though one thing I feel is missing: Facilities to delete fields in a JSON_Value object, but I'm sure these will come as the package matures.

For the sake of completeness, here's the full listing of our penguin example:



I hope you've enjoyed reading this short introduction to the GNATCOLL.JSON package. Ada and JSON is a pretty good match, so if you don't need or want all the complexities of XML, then give JSON a chance. The tools are available and they are pretty good.