July 17, 2018

JSON is simple. It’s easy to read and easy to parse. It’s hierarchical. It has arrays, strings, objects, and more. It would be nice for configuration files but the official spec is lacking a few critical features. JSON doesn’t have comments. It doesn’t have a ‘heredoc’ syntax. You can’t do basic arithmetic either. I had some time on my hands and thus libaejson was born to add the features I wanted.

The library has a liberal MIT license. If you choose to use it please let me know! I’d love to know where it ends up!

Features

It is critical to have comments in configuration files. It is not reasonable to expect users to refer to a manual when configuring software. Since JSON doesn’t support that I decided I’d make my own parser that does. To be fair there are many parsers that do this. I’ve personally used the parser distributed with libbson which supported comments. The API supplied by libbson was well designed and I liked using it. At some point the project switched to a different parser that didn’t support comments.

While comments are required for a good user experience there are other features that are critical to developers. When I wrote libaejson I settled on these extesions to the standard:

  • c-style single and multi-line comments
  • here-doc syntax for strings
  • support for integer and floating point in the API
  • minimal dependencies
  • very easy queries of parsed data
  • basic arithmetic that reduces to number
  • small footprint with minimal dependencies

Examples

{
    /* multi line comments, 
       oh my!
    */
    // single line too!  Yay!
    "a":[0,1,2,3,4,5],
    // this is parsed as a single integer: 2048
    "b":1024 * 2,
    "heredoc":<<<
a
b
c
>>>
}

Use

The typical usage is to parse JSON text from a string or a file into an aejson object and then extract values using a single query string.

The library utilizes memory pools. Internally the parser will allocate memory as needed from the pool that is provided. All of the allocated memory is freed when the pool is freed (in a single operation).

Parsing

/* prepare a minimum 5MB memory pool */
ae_pool_t pool;

/* this is required because libae requires that all objects be set to
   zero prior to initializing */
AE_MEM_CLEAR(&pool);
AE_TRY(ae_pool_init(e, &pool, 1024*1024 * 5));

/* prepare the parser */
aejson_parser_t parser;
AE_MEM_CLEAR(&parser);
AE_TRY(aejson_parser_init(e, &parser));

/* parse stdin */
aejson_object_t *result = NULL;
bool res = aejson_parser_parse_file(e, &parser, &pool, stdin, &result);
fclose(stdin);

/* the only cleanup required is to free the memory pool*/
AE_TRY(ae_pool_uninit(e, &pool));

Querying

Finding data is achieved by querying using the same sort of syntax that you would (I hope) expect. For example “a.b.c” would return the value 64 from the following json {“a”:{“b”:“c”:64}}

A number of functions are available for locating values. They include:

  • aejson_object_find - Finds any value, output is aejson_value_t *
  • aejson_object_find_string - Finds a string
  • aejson_object_find_int64 - Finds an integer
  • aejson_object_find_double - Finds a floating point value
  • aejson_object_find_array_int64 - Finds and duplicates an array of integers
  • aejson_object_find_array_string - Finds an array of strings without duplication
  • aejson_object_find_array_double - Finds and duplicates an array of floating point values.

Example - Querying

In the example below the output will be:

--found--
sup: one
sup: two
sup: three
---------
{
    "s":["one","two","three"],
}
size_t vals_len = 0;
const char *query = "s";
if(!aejson_object_find_array_string(e, result, &pool,
                                    &vals_len, &vals, query))
{
    /* the world has ended */
    abort();
}

printf("--found string array--\n");
for(size_t i=0; i<vals_len; ++i)
{
    printf("sup: %s\n", vals[i]);
}
printf("---------\n");

/* cleanup by calling ae_pool_uninit(e, &pool) */
comments powered by Disqus