Skip to content

Instantly share code, notes, and snippets.

@DaelonSuzuka
Last active March 13, 2020 03:18
Show Gist options
  • Save DaelonSuzuka/87dfd1ae0e90dcefcc4822198efd0353 to your computer and use it in GitHub Desktop.
Save DaelonSuzuka/87dfd1ae0e90dcefcc4822198efd0353 to your computer and use it in GitHub Desktop.
JSON node representation
#ifndef _JSON_NODE_H_
#define _JSON_NODE_H_
/* ************************************************************************** */
/* JSON node type
The contents of a json_node_t(see below) are in a void pointer, and can't be
correctly deferenced without knowing what type to expect. This enum contains
all the possible types you can expect in a node.
[*] these types aren't currently implemented, sorry
*/
typedef enum {
nControl, //
nNodeList, //
nObject, // [*]
nKey, //
nString, //
nFloat, //
nDouble, // [*]
nU8, // [*]
nU16, //
nU24, // [*]
nU32, //
nS8, // [*]
nS16, // [*]
nS24, // [*]
nS32, // [*]
nBool, // [*]
nNull, // [*]
} node_type_t;
/* JSON node
A JSON object is represented as an array of node structs.
*/
typedef struct {
node_type_t type;
void *contents;
} json_node_t;
/* ************************************************************************** */
/* JSON Nodes:
This file describes a C data structure that can be used to represent a JSON
object so that it can be serialized and printed out a UART. The JSON object
is described as an array of json_node_t structs, each one corresponding to
a token in the original JSON object.
* Let's start with an example. *
C data that you're trying to print
const char *billsName = "Bill";
uint16_t billsAge = 56;
Example node representation:
const json_node_t jsonBill[] = {
{nControl, "{"}, // 2
{nKey, "name"}, // 3
{nString, &billsName}, // 4
{nKey, "age"}, // 5
{nU16, &billsAge}, // 6
{nControl, "}"}, // 7
{nControl, NULL}, //
};
const json_node_t jsonPerson[] = {
{nControl, "{"}, // 0
{nKey, "person"}, // 1
{nNodeList, &jsonBill}, //
{nControl, "\e"}, // 9
};
Resulting JSON object, with each 'token' numbered:
0 1 2 3 4 5 6 7 8 9
{ "person": { "name": "Bill", "age": 56 } } end
JSON is a heirarchical key:value format. A 'value' can be a JSON data type,
but it can also be another key:value pair, or multiple key:value pairs.
These structures can be nested arbitrarily deep, like this:
{key:val}
{key: {key:val}} <- note the double closing braces
{key: {key:val, key:val}}
Because of this property, in order to evaluate a node, you need to know
where that node lives in the overall heirarchy. This hierarchy is described
explicitly using nControl nodes, and implicitly using nKey nodes.
* nControl: (a pointer to a C string literal)
Control nodes provide contextual information about the structure of
the JSON object being printed. Control nodes can indicate one of
several different actions:
"{" - prints a '{', starting a new object
"}" - prints a '}', ending the current object
NULL - the end of a node array that's been referenced by another
"\e" - the end of the top-level node array
The printer keeps track of how deeply the objects are nested. If
there are any objects that are still open when the printer hits a
"\e" node, it will print enough "}"s to balance any unmatched "{"s.
Knowing this, go back and reread the example, and match each token
in the output to the node that generates it. Don't worry, I'll wait.
Did you find it?
Token 8 in the output doesn't have a node. Don't panic, your JSON
serializer hasn't developed sentience(yet), it just keeps track of
every time it prints a '{' or '}', and when if there are open braces
that don't have matching closing braces by the time it hits the "\e"
control node, it'll produce them automatically. This feature makes
it very slightly more pleasant to write short JSON definitions.
* nKey: (a pointer to a C string literal)
An nKey node is identical to an nString(described later) node,
except that when evaluated, it's followed by a colon (':').
There's an additional special node type, that can be used to "factor out" a
section of a node array. This can be used if you feel that your JSON
description is getting too long to read clearly, or if you have multiple
JSON descriptions that contain identical sections and you don't want to
repeat yourself.
* nNodeList: (a pointer to another array of nodes)
This node is a pointer to another array of nodes, which are simply
inserted in place of the current node. This insertion process
doesn't add any additional JSON punctuation, so if you want the
included nodes to be in their own object, you'll need to explicitly
add nControl nodes describing the structure.
The remaining node types don't have any special behaviour, they're simply a
reference to some C data. There are node types that correspond to each of
the JSON data types.
number - nFloat, nDouble, nUxx, nSxx
string - nString
boolean - nBool[*]
array - nArray[*]
object - nObject[*]
null - nNull[*]
[*] This node type isn't implemented, yet. Sorry.
* nFloat, nDouble, nUxx, nSxx: (a pointer to a C 'number')
JSON doesn't understand different 'types' of numbers because it's
derived from Javascript, which is dynamicly and weakly typed.
C is statically typed and quite particular, because in order to
interact with a variable(especially via a pointer), it needs to know
how much memory it occupies and how to interpret that memory.
Because the contents of a node are stored as a void pointer,
accessing those contents requires casting the pointer to a
particular type so that C knows how to access it. JSON's attitude of
it's a number, lol" isn't going to be sufficient. Therefore, we need
a node type for each of the C primitive data types:
floats - nFloat, nDouble
unsigned ints - nU8, nU16, nU24, nU32
signed ints - nS8, nS16, nS24, nS32
* nString: (a pointer a C string literal)
JSON strings are delimited by double quotes ('"'). Since it would be
really annoying to require every string literal to include it's own
escaped quotes, the printer handles adding those quotes for you.
* nBool:
I'm not sure how you'd really have a pointer a 1 bit bool, so this
node type will need some additional work to make it functional.
* nArray:
This is quite a bit more complicated that a simple data type. An
array of primitives would require defining the length of the array
and the data type of its elements. This could be handled by making
the nArray node point to some kind of struct containing the length,
type, and a pointer to the actual array to be printed.'
I'm totally stumped on how I'd represent an array of JSON objects,
which is a very common
* nObject:
Most of the reason to use this JSON type is handled by using control
nodes.
* nNull:
This one's pretty easy, it just prints "null" with no quotes.
*/
/* ************************************************************************** */
/* JSON array
A JSON node only contains a single void pointer, which isn't enough
information to properly reference an array in C. You also need to know the
length of the array, the type of its elements, and the location of the array
itself.
node_type_t contains several values that don't make sense in this context,
but since it's defined right there(^), we'll use it anyways.
This feature actually isn't implemented yet, but I'm leaving this here as a
starting point for when I do.
*/
typedef struct {
int length;
node_type_t type;
void *array;
} json_array_t;
/* ************************************************************************** */
#endif // _JSON_NODE_H_
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment