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 stringaejson_object_find_int64
- Finds an integeraejson_object_find_double
- Finds a floating point valueaejson_object_find_array_int64
- Finds and duplicates an array of integersaejson_object_find_array_string
- Finds an array of strings without duplicationaejson_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) */