Loading...
Searching...
No Matches
JSON to BSON converter

Now that we have a basic grasp of the library, we'll write a solution to a real life problem: converting JSON to BSON.

Our program will expect correctly formatted JSON, in condensed one-line format, and will output a BSON document for each line of JSON received.

#define __STRICT_ANSI__ 1
#include <bson.h>
#include <json.h>
#include <stdio.h>
#include <unistd.h>
#include <glib.h>

First, we forward declare the json_to_bson() function, because we'll recursively use it later on:

static bson *json_to_bson (struct json_object *json);

Next, we create the heart of the program, a function that takes a BSON object, a value and a key, and appends the key-value pair to the bson object, with the correct type.

static void
json_key_to_bson_key (bson *b, void *val,
const gchar *key)
{

We do this by checking the JSON object's type, and acting up on it:

switch (json_object_get_type (val))
{

The boolean, double, integer and string types are easy: we just use the appropriate bson_append_*() function:

case json_type_boolean:
bson_append_boolean (b, key, json_object_get_boolean (val));
break;
bson_append_boolean
gboolean bson_append_boolean(bson *b, const gchar *name, gboolean value)
Append a boolean to a BSON object.
Definition bson.c:675
case json_type_double:
bson_append_double (b, key, json_object_get_double (val));
break;
bson_append_double
gboolean bson_append_double(bson *b, const gchar *name, gdouble val)
Append a double to a BSON object.
Definition bson.c:614
case json_type_int:
bson_append_int32 (b, key, json_object_get_int (val));
break;
bson_append_int32
gboolean bson_append_int32(bson *b, const gchar *name, gint32 i)
Append a 32-bit integer to a BSON object.
Definition bson.c:761
case json_type_string:
bson_append_string (b, key, json_object_get_string (val), -1);
break;
bson_append_string
gboolean bson_append_string(bson *b, const gchar *name, const gchar *val, gint32 length)
Append a string to a BSON object.
Definition bson.c:626

Converting a JSON object to BSON is a bit more complicated, yet, straightforward nevertheless:

case json_type_object:
{
bson *sub;
sub = json_to_bson (val);
bson_append_document (b, key, sub);
bson_free (sub);
break;
}
bson_append_document
gboolean bson_append_document(bson *b, const gchar *name, const bson *doc)
Append a BSON document to a BSON object.
Definition bson.c:633
bson_free
void bson_free(bson *b)
Free the memory associated with a BSON object.
Definition bson.c:579

This is one of the reasons we needed to forward-declare json_to_bson(): we're using it to turn the JSON value into BSON, and append it as a subdocument.

Next up: arrays! This is even trickier than sub-documents, as we need to iterate over the elements, and append each individually. But, trickier as it may be, it's still straightforward;

case json_type_array:
{
gint pos;
bson *sub;
sub = bson_new ();
for (pos = 0; pos < json_object_array_length (val); pos++)
{
gchar *nk = g_strdup_printf ("%d", pos);
json_key_to_bson_key (sub, json_object_array_get_idx (val, pos),
nk);
g_free (nk);
}
bson_new
bson * bson_new(void)
Create a new BSON object.
Definition bson.c:252
bson_finish (sub);
bson_append_array (b, key, sub);
bson_free (sub);
break;
}
bson_append_array
gboolean bson_append_array(bson *b, const gchar *name, const bson *array)
Append a BSON array to a BSON object.
Definition bson.c:639
bson_finish
gboolean bson_finish(bson *b)
Finish a BSON object.
Definition bson.c:521

Anything else, we ignore:

default:
break;
}
}

And to bind this together with JSON-C's API, we need two more functions. The first one will simply iterate over a JSON object, and call the function we wrote above:

static void
json_to_bson_foreach (bson *b, struct json_object *json)
{
json_object_object_foreach (json, key, val)
{
json_key_to_bson_key (b, val, key);
}
}

The next one is another wrapper around this former: it creates a BSON document, calls the foreach method, then finishes the BSON object and we're done:

static bson *
json_to_bson (struct json_object *json)
{
bson *b;
b = bson_new ();
json_to_bson_foreach (b, json);
bson_finish (b);
return b;
}

We're almost done! All that is left is writing our program's entry point: something that will read the input, turn it into BSON, and write it out:

int
main (int argc, char **argv)
{
GIOChannel *input;
GString *json_str;
GError *error = NULL;
struct json_tokener *tokener;

We do some setting up, creating a new IO channel, and a JSON tokenizer:

input = g_io_channel_unix_new (0);
json_str = g_string_new (NULL);
tokener = json_tokener_new ();

Then, until we have something to read...

while (g_io_channel_read_line_string (input, json_str,
NULL, &error) == G_IO_STATUS_NORMAL)
{
struct json_object *json;
bson *bson;

We reset the tokenizer before parsing another line, then parse the JSON we received:

json_tokener_reset (tokener);
json = json_tokener_parse_ex (tokener, json_str->str, json_str->len);
if (!json)
{
fprintf (stderr, "Error parsing json: %s\n", json_str->str);
break;
}

If we received something other than a JSON object, we can't turn that into BSON, so we write an error to STDERR, and skip this line:

if (json_object_get_type (json) != json_type_object)
{
fprintf (stderr,
"Error: json's top-level object is not object: %s\n",
json_str->str);
json_object_put (json);
break;
}

Otherwise, we turn it into BSON, and write it to STDOUT:

bson = json_to_bson (json);
json_object_put (json);
write (1, bson_data (bson), bson_size (bson));
bson_free (bson);
bson_size
gint32 bson_size(const bson *b)
Return the size of a finished BSON object.
Definition bson.c:542
bson_data
const guint8 * bson_data(const bson *b)
Return the raw bytestream form of the BSON object.
Definition bson.c:554
}

And that was our program, a very simple application that turns each line of JSON into BSON.

return 0;
}