Skip to content

JSON parser and generator for C/C++ with scanf/printf like interface. Targeting embedded systems.

License

Notifications You must be signed in to change notification settings

cesanta/frozen

Repository files navigation

JSON parser and emitter for C/C++

Features

  • ISO C and ISO C++ compliant portable code
  • Very small footprint
  • No dependencies
  • json_scanf() scans a string directly into C/C++ variables
  • json_printf() prints C/C++ variables directly into an output stream
  • json_setf() modifies an existing JSON string
  • json_fread() reads JSON from a file
  • json_fprintf() writes JSON to a file
  • Built-in base64 encoder and decoder for binary data
  • Parser provides low-level callback API and high-level scanf-like API
  • 100% test coverage
  • Used in Mongoose OS, an operating system for connected commercial products on low-power microcontrollers

API reference

json_scanf(), json_vscanf

intjson_scanf(constchar*str, intstr_len, constchar*fmt, ...); intjson_vscanf(constchar*str, intstr_len, constchar*fmt, va_listap); /* json_scanf's %M handler */typedefvoid (*json_scanner_t)(constchar*str, intlen, void*user_data);

Scans the JSON string str, performing scanf-like conversions according to fmt. fmt uses scanf()-like format, with the following differences:

  1. Object keys in the format string don't have to be quoted, e.g. "{key: %d}"
  2. Order of keys in the format string does not matter, and the format string may omit keys to fetch only those that are of interest, for example, assume str is a JSON string {"a": 123, "b": "hi", c: true }. We can fetch only the value of the c key:
    intvalue=0; json_scanf(str, strlen(str), "{c: %B}", &value);
  3. Several extra format specifiers are supported:
    • %B: consumes int * (or char *, if sizeof(bool) == sizeof(char)), expects boolean true or false.
    • %Q: consumes char **, expects quoted, JSON-encoded string. Scanned string is malloc-ed, caller must free() the string.
    • %V: consumes char **, int *. Expects base64-encoded string. Result string is base64-decoded, malloced and NUL-terminated. The length of result string is stored in int * placeholder. Caller must free() the result.
    • %H: consumes int *, char **. Expects a hex-encoded string, e.g. "fa014f". Result string is hex-decoded, malloced and NUL-terminated. The length of the result string is stored in int * placeholder. Caller must free() the result.
    • %M: consumes custom scanning function pointer and void *user_data parameter - see json_scanner_t definition.
    • %T: consumes struct json_token *, fills it out with matched token.

Returns the number of elements successfully scanned & converted. Negative number means scan error.

Example - scan arbitrary JSON string:

// str has the following JSON string (notice keys are out of order)://{"a": 123, "d": true, "b": [1, 2], "c": "hi" }inta=0, d=0; char*c=NULL; void*my_data=NULL; json_scanf(str, strlen(str), "{a:%d, b:%M, c:%Q, d:%B }", &a, scan_array, my_data, &c, &d); // This function is called by json_scanf() call above.// str is "[1, 2]", user_data is my_data.staticvoidscan_array(constchar*str, intlen, void*user_data){structjson_tokent; inti; printf("Parsing array: %.*s\n", len, str); for (i=0; json_scanf_array_elem(str, len, "", i, &t) >0; i++){printf("Index %d, token [%.*s]\n", i, t.len, t.ptr)} }

Example - parse array of objects:

// str has the following JSON string - array of objects://{"a": [{"b": 123},{"b": 345} ] }// This example shows how to iterate over array, and parse each object.inti, value, len=strlen(str); structjson_tokent; for (i=0; json_scanf_array_elem(str, len, ".a", i, &t) >0; i++){// t.type == JSON_TYPE_OBJECTjson_scanf(t.ptr, t.len, "{b: %d}", &value); // value is 123, then 345 }

json_scanf_array_elem()

intjson_scanf_array_elem(constchar*s, intlen, constchar*path, intindex, structjson_token*token);

A helper function to scan an array item with given path and index. Fills token with the matched JSON token. Returns -1 if no array element found, otherwise non-negative token length.

json_printf()

Frozen printing API is pluggable. Out of the box, Frozen provides a way to print to a string buffer or to an opened file stream. It is easy to tell Frozen to print to another destination, for example, to a socket, etc. Frozen does this by defining an "output context" descriptor which has a pointer to a low-level printing function. If you want to print to another destination, just define your specific printing function and initialise output context with it.

This is the definition of the output context descriptor:

structjson_out{int (*printer)(structjson_out*, constchar*str, size_tlen); union{struct{char*buf; size_tsize; size_tlen} buf; void*data; FILE*fp} u};

Frozen provides two helper macros to initialise two built-in output descriptors:

structjson_outout1=JSON_OUT_BUF(buf, len); structjson_outout2=JSON_OUT_FILE(fp);
typedefint (*json_printf_callback_t)(structjson_out*, va_list*ap); intjson_printf(structjson_out*, constchar*fmt, ...); intjson_vprintf(structjson_out*, constchar*fmt, va_listap);

Generate formatted output into a given string buffer, auto-escaping keys. This is a superset of printf() function, with extra format specifiers:

  • %B print json boolean, true or false. Accepts an int.
  • %Q print quoted escaped string or null. Accepts a const char *.
  • %.*Q same as %Q, but with length. Accepts int, const char *
  • %V print quoted base64-encoded string. Accepts a const char *, int.
  • %H print quoted hex-encoded string. Accepts a int, const char *.
  • %M invokes a json_printf_callback_t function. That callback function can consume more parameters.

Return number of bytes printed. If the return value is bigger than the supplied buffer, that is an indicator of overflow. In the overflow case, overflown bytes are not printed.

Example:

json_printf(&out, "{%Q: %d, x: [%B, %B], y: %Q}", "foo", 123, 0, -1, "hi"); // Result://{"foo": 123, "x": [false, true], "y": "hi"}

To print a complex object (for example, serialise a structure into an object), use %M format specifier:

structmy_struct{inta, b} mys={1,2}; json_printf(&out, "{foo: %M, bar: %d}", print_my_struct, &mys, 3); // Result://{"foo":{"a": 1, "b": 2}, "bar": 3}
intprint_my_struct(structjson_out*out, va_list*ap){structmy_struct*p=va_arg(*ap, structmy_struct*); returnjson_printf(out, "{a: %d, b: %d}", p->a, p->b)}

json_printf_array()

intjson_printf_array(structjson_out*, va_list*ap);

A helper %M callback that prints contiguous C arrays. Consumes void *array_ptr, size_t array_size, size_t elem_size, char *fmt Returns number of bytes printed.

json_walk() - low level parsing API

/* JSON token type */enumjson_token_type{JSON_TYPE_INVALID=0, /* memsetting to 0 should create INVALID value */JSON_TYPE_STRING, JSON_TYPE_NUMBER, JSON_TYPE_TRUE, JSON_TYPE_FALSE, JSON_TYPE_NULL, JSON_TYPE_OBJECT_START, JSON_TYPE_OBJECT_END, JSON_TYPE_ARRAY_START, JSON_TYPE_ARRAY_END, JSON_TYPES_CNT, }; /* * Structure containing token type and value. Used in `json_walk()` and * `json_scanf()` with the format specifier `%T`. */structjson_token{constchar*ptr; /* Points to the beginning of the value */intlen; /* Value length */enumjson_token_typetype; /* Type of the token, possible values are above */ }; /* Callback-based API */typedefvoid (*json_walk_callback_t)(void*callback_data, constchar*name, size_tname_len, constchar*path, conststructjson_token*token); /* * Parse `json_string`, invoking `callback` in a way similar to SAX parsers; * see `json_walk_callback_t`. */intjson_walk(constchar*json_string, intjson_string_length, json_walk_callback_tcallback, void*callback_data);

json_walk() is a low-level, callback based parsing API. json_walk() calls a given callback function for each scanned value.

Callback receives a name, a path to the value, a JSON token that points to the value and an arbitrary user data pointer.

The path is constructed using this rule:

  • Root element has "" (empty string) path
  • When an object starts, . (dot) is appended to the path
  • When an object key is parsed, a key name is appended to the path
  • When an array is parsed, an [ELEMENT_INDEX] is appended for each element

For example, consider the following json string: {"foo": 123, "bar": [ 1, 2,{"baz": true } ] }. The sequence of callback invocations will be as follows:

  • type: JSON_TYPE_OBJECT_START, name: NULL, path: "", value: NULL
  • type: JSON_TYPE_NUMBER, name: "foo", path: ".foo", value: "123"
  • type: JSON_TYPE_ARRAY_START, name: "bar", path: ".bar", value: NULL
  • type: JSON_TYPE_NUMBER, name: "0", path: ".bar[0]", value: "1"
  • type: JSON_TYPE_NUMBER, name: "1", path: ".bar[1]", value: "2"
  • type: JSON_TYPE_OBJECT_START, name: "2", path: ".bar[2]", value: NULL
  • type: JSON_TYPE_TRUE, name: "baz", path: ".bar[2].baz", value: "true"
  • type: JSON_TYPE_OBJECT_END, name: NULL, path: ".bar[2]", value: "{\"baz\": true }"
  • type: JSON_TYPE_ARRAY_END, name: NULL, path: ".bar", value: "[ 1, 2,{\"baz\": true } ]"
  • type: JSON_TYPE_OBJECT_END, name: NULL, path: "", value: "{\"foo\": 123, \"bar\": [ 1, 2,{\"baz\": true } ] }"

If top-level element is an array: [1,{"foo": 2}]

  • type: JSON_TYPE_ARRAY_START, name: NULL, path: "", value: NULL
  • type: JSON_TYPE_NUMBER, name: "0", path: "[0]", value: "1"
  • type: JSON_TYPE_OBJECT_START, name: "1", path: "[1]", value: NULL
  • type: JSON_TYPE_NUMBER, name: "foo", path: "[1].foo", value: "2"
  • type: JSON_TYPE_OBJECT_END, name: NULL, path: "[1]", value: "{\"foo\": 2}"
  • type: JSON_TYPE_ARRAY_END, name: NULL, path: "", value: "[1,{"foo": 2}]"

If top-level element is a scalar: true

  • type: JSON_TYPE_TRUE, name: NULL, path: "", value: "true"

json_walk_args() - low level parsing API extensible interface

This function is identical to json_walk() except that it takes a struct pointer argument for the callback and callback_data arguments and additional configuration elements:

struct frozen_args{json_walk_callback_t callback; void *callback_data; int limit}; 

This struct must be initialized using INIT_FROZEN_ARGS() to retain forward compatibility before any members are set as illustrated here:

struct frozen_args args[1]; INIT_FROZEN_ARGS(args); args->callback = mycb; args->callback_data = data; args->limit = 20; ret = json_walk_args(string, len, args); 

the limit member of struct frozen_args can be set to limit the maximum recursion depth to prevent possible stack overflows and limit parsing complexity.

json_fprintf(), json_vfprintf()

/* * Same as json_printf, but prints to a file. * File is created if does not exist. File is truncated if already exists. */intjson_fprintf(constchar*file_name, constchar*fmt, ...); intjson_vfprintf(constchar*file_name, constchar*fmt, va_listap);

json_asprintf(), json_vasprintf()

/* * Print JSON into an allocated 0-terminated string. * Return allocated string, or NULL on error. * Example: * * ```c * char *str = json_asprintf("{a:%H}", 3, "abc"); * printf("%s\n", str); // Prints "616263" * free(str); * ``` */char*json_asprintf(constchar*fmt, ...); char*json_vasprintf(constchar*fmt, va_listap);

json_fread()

/* * Read the whole file in memory. * Return malloc-ed file content, or NULL on error. The caller must free(). */char*json_fread(constchar*file_name);

json_setf(), json_vsetf()

/* * Update given JSON string `s,len` by changing the value at given `json_path`. * The result is saved to `out`. If `json_fmt` == NULL, that deletes the key. * If path is not present, missing keys are added. Array path without an * index pushes a value to the end of an array. * Return 1 if the string was changed, 0 otherwise. * * Example: s is a JSON string{"a": 1, "b": [ 2 ] } * json_setf(s, len, out, ".a", "7"); //{"a": 7, "b": [ 2 ] } * json_setf(s, len, out, ".b", "7"); //{"a": 1, "b": 7 } * json_setf(s, len, out, ".b[]", "7"); //{"a": 1, "b": [ 2,7 ] } * json_setf(s, len, out, ".b", NULL); //{"a": 1 } */intjson_setf(constchar*s, intlen, structjson_out*out, constchar*json_path, constchar*json_fmt, ...); intjson_vsetf(constchar*s, intlen, structjson_out*out, constchar*json_path, constchar*json_fmt, va_listap);

json_prettify()

/* * Pretty-print JSON string `s,len` into `out`. * Return number of processed bytes in `s`. */intjson_prettify(constchar*s, intlen, structjson_out*out);

json_prettify_file()

/* * Prettify JSON file `file_name`. * Return number of processed bytes, or negative number of error. * On error, file content is not modified. */intjson_prettify_file(constchar*file_name);

json_next_key(), json_next_elem()

/* * Iterate over an object at given JSON `path`. * On each iteration, fill the `key` and `val` tokens. It is OK to pass NULL * for `key`, or `val`, in which case they won't be populated. * Return an opaque value suitable for the next iteration, or NULL when done. * * Example: * * ```c * void *h = NULL; * struct json_token key, val; * while ((h = json_next_key(s, len, h, ".foo", &key, &val)) != NULL){ * printf("[%.*s] -> [%.*s]\n", key.len, key.ptr, val.len, val.ptr); * } * ``` */void*json_next_key(constchar*s, intlen, void*handle, constchar*path, structjson_token*key, structjson_token*val); /* * Iterate over an array at given JSON `path`. * Similar to `json_next_key`, but fills array index `idx` instead of `key`. */void*json_next_elem(constchar*s, intlen, void*handle, constchar*path, int*idx, structjson_token*val);

Minimal mode

By building with -DJSON_MINIMAL=1 footprint can be significantly reduced. The following limitations apply in this configuration:

  • Only integer numbers are supported. This affects parsing and %f/%lf conversions in printf and scanf.
  • Hex ('%H') and base64 (%V) conversions are disabled.

Examples

Print JSON configuration to a file

json_fprintf("settings.json", "{a: %d, b: %Q }", 123, "string_value"); json_prettify_file("settings.json"); // Optional

Read JSON configuration from a file

structmy_config{inta; char*b} c={.a=0, .b=NULL }; char*content=json_fread("settings.json"); json_scanf(content, strlen(content), "{a: %d, b: %Q}", &c.a, &c.b);

Modify configuration setting in a JSON file

constchar*settings_file_name="settings.json", *tmp_file_name="tmp.json"; char*content=json_fread(settings_file_name); FILE*fp=fopen(tmp_file_name, "w"); structjson_outout=JSON_OUT_FILE(fp); json_setf(content, strlen(content), &out, ".b", "%Q", "new_string_value"); fclose(fp); json_prettify_file(tmp_file_name); // Optionalrename(tmp_file_name, settings_file_name);

Contributions

To submit contributions, sign Cesanta CLA and send GitHub pull request.

Licensing

Frozen is released under the Apache 2.0 license.

For commercial support and professional services, contact us.

See also

  • Mongoose Web Server Library - a robust, open-source solution licensed under GPLv2, designed to seamlessly integrate web server functionality into your embedded devices.
  • With complementary Mongoose Wizard - a no-code visual tool that enables rapid WebUI creation without the need for frontend expertise.

About

JSON parser and generator for C/C++ with scanf/printf like interface. Targeting embedded systems.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 15