- Portable to any environment
- Simple, easy to understand API
- Very small footprint
- No dependencies
- Code is strict ISO C and strict ISO C++ at the same time
- Supports superset of JSON: allows non-quoted identifiers as object keys
- Complete 100% test coverage
-
Copy
frozen.c
andfrozen.h
to your project -
Add
frozen.c
to the list of source files -
Parsing with Frozen is done in two steps: first, split JSON string into tokens by
parse_json()
orparse_json2()
. Second, search for certain parameters in parsed string byfind_json_token()
. Below is an example, error handling is omitted for clarity:#include <stdio.h> #include "frozen.h"
int main(void) { static const char *json = " { foo: 1, bar: 2 } "; struct json_token *arr, *tok;
// Tokenize json string, fill in tokens array arr = parse_json2(json, strlen(json)); // Search for parameter "bar" and print it's value tok = find_json_token(arr, "bar"); printf("Value of bar is: [%.*s]\n", tok->len, tok->ptr); // Do not forget to free allocated tokens array free(arr); return 0;
}
int parse_json(const char *json_string, int json_string_length,
struct json_token *tokens_array, int size_of_tokens_array);
struct json_token *parse_json2(const char *json_string, int string_length);
parse_json()
and parse_json2()
parse JSON string.
parse_json()
needs pre-allocated tokens array or NULL, whereas
parse_json2()
allocates tokens array automatically.
parse_json()
tokenizes json_string
of length json_string_length
.
If tokens_array
is not NULL
, then parse_json()
will store tokens
in the tokens_array
. Token with type
JSON_TYPE_EOF
marks the end of parsed tokens. JSON token is defined as:
struct json_token {
const char *ptr; // Points to the beginning of the token
int len; // Token length
int num_desc; // For arrays and object, total number of descendants
int type; // Type of the token, possible values below
#define JSON_TYPE_EOF 0 // Not a real token, but end-of-tokens marker
#define JSON_TYPE_STRING 1
#define JSON_TYPE_NUMBER 2
#define JSON_TYPE_OBJECT 3
#define JSON_TYPE_TRUE 4
#define JSON_TYPE_FALSE 5
#define JSON_TYPE_NULL 6
#define JSON_TYPE_ARRAY 7
};
If tokens_array
is NULL
, then parse_json
just checks the validity of
the JSON string, and points where parsing stops. If tokens_array
is not
NULL
, it must be pre-allocated by the caller. Note that struct json_token
just points to the data inside json_string
, it does not own the data. Thus
the token's lifetime is identical to the lifetime of json_string
, until
json_string
is freed or mutated.
Return: On success, an offset inside json_string
is returned
where parsing has finished. On failure, a negative number is
returned, one of:
#define JSON_STRING_INVALID -1
#define JSON_STRING_INCOMPLETE -2
#define JSON_TOKEN_ARRAY_TOO_SMALL -3
parse_json2()
returns NULL on error and non-NULL on success.
Below is an illustration on how JSON string gets tokenized:
JSON string: { "key_1" : "value_1", "key_2": [ 12345, null ] }
JSON_TYPE_OBJECT |<-------------------------------------------------->|
JSON_TYPE_STRING |<->|
JSON_TYPE_STRING |<--->|
JSON_TYPE_STRING |<->|
JSON_TYPE_ARRAY |<------------>|
JSON_TYPE_NUMBER |<->|
JSON_TYPE_NULL |<>|
JSON_TYPE_EOF
const struct json_token *find_json_token(const struct json_token *toks,
const char *path);
This is a convenience function to fetch specific values from the parsed
string. toks
must be a valid array, successfully populated by parse_json()
.
A path
is a string, an accessor to the desired element of the JSON object,
as if it was written in Javascript. For example, if parsed JSON string is
"{ foo : { bar: [1, 2, 3] } }"
, then path "foo.bar[0]"
would return a token
that points to number "1"
.
Return: pointer to the found token, or NULL on failure.
int json_emit_long(char *buf, int buf_len, long value);
int json_emit_double(char *buf, int buf_len, double value);
int json_emit_quoted_str(char *buf, int buf_len, const char *str);
int json_emit_unquoted_str(char *buf, int buf_len, const char *str);
These functions are used to generate JSON string. All of them accept
a destination buffer and a value to output, and return number of bytes printed.
Returned value can be bigger then destination buffer size, this is an
indication of overflow. If there is no overflow, a buffer is guaranteed to
be nul-terminated. Numbers are printed by json_emit_double()
and
json_emit_int()
functions, strings are printed by json_emit_quoted_str()
function. Values for null
, true
, false
, and characters
{
, }
, [
, ]
, ,
, :
are printed by
json_emit_raw_str()
function.
int json_emit(char *buf, int buf_len, const char *format, ...);
A convenience function that generates JSON string using formatted output.
Characters allowed in format
string:
[
, ]
, {
, }
, ,
, :
, \r
, \n
, \t
,
: these characters
are appended to the output buffer as-is
i
: argument must be an long
value, outputs number
f
: argument must be a double
value, outputs number
v
: arguments must be a char *
value, followed by size_t
value,
outputs quoted string
V
: arguments must be a char *
value, followed by size_t
value,
outputs unquoted string
s
: arguments must be a \0
-terminated char *
value, outputs quoted string
S
: arguments must be a \0
-terminated char *
value, outputs unquoted string
N
: outputs null
T
: outputs true
F
: outputs false
#include "frozen.h"
static const char *config_str = " { ports: [ 80, 443 ] } ";
struct json_token tokens[10];
int tokens_size = sizeof(tokens) / sizeof(tokens[0]);
// Parse config string and make sure tokenization is correct
ASSERT(parse_json(config_str, strlen(config_str), tokens, tokens_size) > 0);
ASSERT(tokens[0].type == JSON_TYPE_OBJECT); // Tokens are populated
ASSERT(tokens[1].type == JSON_TYPE_STRING); // in order of their
ASSERT(tokens[2].type == JSON_TYPE_ARRAY); // appearance in the
ASSERT(tokens[3].type == JSON_TYPE_NUMBER); // JSON string
ASSERT(tokens[4].type == JSON_TYPE_NUMBER);
ASSERT(tokens[5].type == JSON_TYPE_EOF); // Last token is always EOF
// Fetch port values
ASSERT(find_json_token(tokens, "ports") == &tokens[2]);
ASSERT(find_json_token(tokens, "ports[0]") == &tokens[3]);
ASSERT(find_json_token(tokens, "ports[1]") == &tokens[4]);
ASSERT(find_json_token(tokens, "ports[3]") == NULL); // Outside boundaries
ASSERT(find_json_token(tokens, "foo.bar") == NULL); // Nonexistent
char buf[1000];
json_emit(buf, sizeof(buf), "{ s: [i, T, F, N] }", "foo", (long) -123);
People who have agreed to the Cesanta CLA can make contributions. Note that the CLA isn't a copyright assigment but rather a copyright license. You retain the copyright on your contributions.
Frozen is released under commercial and GNU GPL v.2 open source licenses.
Commercial Projects: Once your project becomes commercialised GPLv2 licensing dictates that you need to either open your source fully or purchase a commercial license. Cesanta offer full, royalty-free commercial licenses without any GPL restrictions. If your needs require a custom license, we’d be happy to work on a solution with you. [Contact us for pricing.] (https://www.cesanta.com/contact)
Prototyping: While your project is still in prototyping stage and not for sale, you can use Frozen’s open source code without license restrictions.