Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/rdpoor/jemi

jemi: a compact, trusting, JSON serializer with static allocation in pure C for embedded systems
https://github.com/rdpoor/jemi

Last synced: 3 months ago
JSON representation

jemi: a compact, trusting, JSON serializer with static allocation in pure C for embedded systems

Awesome Lists containing this project

README

        

# jemi
jemi: a compact, pure-C JSON serializer for embedded systems

## Overview

`jemi` makes it easy to construct complex JSON structures and then emit them
to a buffer, a string or a stream. Designed specifically for embedded systems,
`jemi` is:
* **compact**: one source file and one header file
* **portable**: written in pure C
* **deterministic**: `jemi` uses user-provided data structures and never calls
`malloc()`.
* **yours to use**: `jemi` is covered under the permissive MIT License.

`jemi` derives much of its efficiency and small footprint by a philosophy of
trust: Rather than provide rigorous error checking of input parameters,
`jemi` instead assumes that you provide valid parameters to function calls.

## A Short Example

`jemi` provides two styles for building JSON structures. The "all in one" style
shown in jemi_example1() lets you use C syntax that parallels the resulting JSON
structure. The "piecewise" style shown in jemi_example2() shows how you can
pre-build pieces of JSON structure to be emitted later. Both examples emit the
same results.

```
#include "jemi.h"
#include

#define JEMI_NODES_MAX 30
static jemi_node_t node_pool[JEMI_NODES_MAX];

/**
* Using the "all in one" constructors to create a compound JSON structure.
*/
void jemi_example1(void) {
jemi_init(node_pool, JEMI_NODES_MAX);

jemi_node_t *root =
jemi_object(
jemi_string("colors"),
jemi_object(
jemi_string("yellow"),
jemi_array(jemi_integer(255), jemi_integer(255), jemi_integer(0), NULL),
jemi_string("cyan"),
jemi_array(jemi_integer(0), jemi_integer(255), jemi_integer(255), NULL),
jemi_string("magenta"),
jemi_array(jemi_integer(255), jemi_integer(0), jemi_integer(255), NULL),
NULL),
NULL);
jemi_emit(root, (void (*)(char))putchar); // casting avoids compiler warning
putchar('\n');
}

/**
* Using the "append" functions to piecewise build a compound JSON structure.
*/
void jemi_example2(void) {
jemi_node_t *root, *dict, *rgb;

jemi_init(node_pool, JEMI_NODES_MAX);

// build from the inside out...
root = jemi_object(NULL);
jemi_object_append(root, jemi_list(jemi_string("colors"), dict = jemi_object(NULL), NULL));
jemi_object_append(dict, jemi_list(jemi_string("yellow"), rgb = jemi_array(NULL), NULL));
jemi_array_append(rgb, jemi_list(jemi_integer(255), jemi_integer(255), jemi_integer(0), NULL));
jemi_object_append(dict, jemi_list(jemi_string("cyan"), rgb = jemi_array(NULL), NULL));
jemi_array_append(rgb, jemi_list(jemi_integer(0), jemi_integer(255), jemi_integer(255), NULL));
jemi_object_append(dict, jemi_list(jemi_string("magenta"), rgb = jemi_array(NULL), NULL));
jemi_array_append(rgb, jemi_list(jemi_integer(255), jemi_integer(0), jemi_integer(255), NULL));

jemi_emit(root, (void (*)(char))putchar); // casting avoids compiler warning
putchar('\n');
}

int main(void) {
jemi_example1();
jemi_example2();
}
```

Both `example1()` and `example2()` output the same JSON, which (when formatted
for redability) looks like this:

```
{
"colors":{
"yellow":[
255,
255,
0
],
"cyan":[
0,
255,
255
],
"magenta":[
255,
0,
255
]
}
}
```

## Advanced Example

Suppose you know the overall JSON structure that you're going to use, but you
want to dynamically add data to various nodes within the structure before
emitting it.

jemi handles this case for you with the following features;
* jemi_copy() makes a copy of any jemi structure (or substructure)
* jemi_list() lets you create a "disembodied list" of items that can be
appended to the body of a jemi_list or jemi_object via jemi_array_append()
and jemi_object_append().
* jemi_xxx_set() lets you update the value of a string, boolean or number node.
* The C syntax `a = fn()` evaluates `fn()` and assigns it to `a` and the entire
expression evaluates to that value. As shown in the example below, you can
exploit this to save references to jemi sub-expressions in the JSON structure.

The result isn't especially pretty, but in practice, you'd create bespoke
functions to fill in and copy your templates and to insert them into your
overall structure.

```
jemi_node_t *snippet_template, *snippets, *color_map, *color_name, *colors,
*red_val, *grn_val, *blu_val;

// create a snippet that will be used as a key/value pair in an object.
snippet_template =
jemi_list(
color_name = jemi_string("color_name"),
jemi_array(red_val=jemi_integer(0),
grn_val=jemi_integer(0),
blu_val=jemi_integer(0),
NULL),
NULL);

// Define the overall JSON structure for our color map
color_map =
jemi_object(
colors = jemi_string("colors"),
snippets = jemi_object(NULL),
NULL);
ASSERT(renders_as(color_map, "{\"colors\":{}}"));

// customize template for yellow and add a copy to the "snippets" sub-structure
jemi_string_set(color_name, "yellow");
jemi_integer_set(red_val, 255);
jemi_integer_set(grn_val, 255);
jemi_integer_set(blu_val, 0);
jemi_object_append(snippets, jemi_copy(snippet_template));
ASSERT(renders_as(color_map, "{\"colors\":{"
"\"yellow\":[255,255,0]"
"}}"));

// customize template for cyan and add a copy to the "snippets" sub-structure
jemi_string_set(color_name, "cyan");
jemi_integer_set(red_val, 0);
jemi_integer_set(grn_val, 255);
jemi_integer_set(blu_val, 255);
jemi_object_append(snippets, jemi_copy(snippet_template));
ASSERT(renders_as(color_map, "{\"colors\":{"
"\"yellow\":[255,255,0],"
"\"cyan\":[0,255,255]"
"}}"));

// customize template for magenta and add a copy to the "snippets" sub-structure
jemi_string_set(color_name, "magenta");
jemi_integer_set(red_val, 255);
jemi_integer_set(grn_val, 0);
jemi_integer_set(blu_val, 255);
jemi_object_append(snippets, jemi_copy(snippet_template));
ASSERT(renders_as(color_map, "{\"colors\":{"
"\"yellow\":[255,255,0],"
"\"cyan\":[0,255,255],"
"\"magenta\":[255,0,255]"
"}}"));

```

## No Guard Rails

jemi trusts that you know what you're doing and that you'll pass valid arguments
to the functions. As such, jemi does very little error checking. If your
app warrants additional error checking, by all means, write wrappers around the
jemi functions to perform whatever level of safety you need.

With that said, there are a few things to keep in mind:

* Don't forget to pass NULL as the last argument to `jemi_array()`,
`jemi_object()`, or `jemi_list()`. Failure to do so will almost certainly
result in some sort of segfault.
* Calling `jemi_float_set()` on a node that's not a number will lead to
unexpected results. Ditto for integer, string and boolean.
* Calling `jemi_array_append()` on a node that's not an array will lead to
unexpected results. Ditto for `jemi_object_append()`. However, it is okay
to call `jemi_list_append()` on any jemi node type or NULL.
* The JSON spec requires that each key of a key/value pair in an object is a
string. However, `jemi_object()` does not enforce this: you can use any jemi
object as a key. Furthermore, it does not check to see that every key has a
corresponding value.