A source xPack for mjson
npm install @displode/mjson-xpack_mjson_ is a JSON parser, emitter and JSON-RPC engine.
- Small, ~1k lines of code, embedded-friendly
- No dependencies
- State machine parser, no allocations, no recursion
- High level API - fetch from JSON directly into C/C++ by
jsonpath
- Low level SAX API
- Flexible JSON generation API - print to buffer, file, socket, etc
- JSON-RPC client/server
``c
const char *s = "{\"a\":1,\"b\":[2,false]}"; // {"a":1,"b":[2,false]}
double val; // Get a attributeval
if (mjson_get_number(s, strlen(s), "$.a", &val)) // into C variable
printf("a: %g\n", val); // a: 1
const char *buf; // Get b sub-objectbuf,len
int len; // into C variables
if (mjson_find(s, strlen(s), "$.b", &buf, &len)) // And print it
printf("%.*s\n", len, buf); // [2,false]
int v; // Extract falsev
if (mjson_get_bool(s, strlen(s), "$.b[1]", &v)) // into C variable `
printf("boolean: %d\n", v); // boolean: 0
`c
// Print into a statically allocated buffer
char buf[100];
mjson_snprintf(buf, sizeof(buf), "{%Q:%d}", "a", 123);
printf("%s\n", buf); // {"a":123}
// Print into a dynamically allocated string
char *s = mjson_aprintf("{%Q:%g}", "a", 3.1415);
printf("%s\n", s); // {"a":3.1415}
free(s); // Don't forget to free an allocated string
`
In the following example, we initialize JSON-RPC context, and call
a couple of JSON-RPC methods: a built-in rpc.list method which listsfoo
all registered methods, and our own method.
The sender() implementation just prints the reply to the standard output,
but in real life it should send a reply to the real remote peer - UART, socket,
or whatever else.
`c
#include "mjson.h"
// A custom RPC handler. Many handlers can be registered.
static void foo(struct jsonrpc_request *r) {
double x;
mjson_get_number(r->params, r->params_len, "$[1]", &x);
jsonrpc_return_success(r, "{%Q:%g,%Q:%Q}", "x", x, "ud", r->userdata);
}
// Sender function receives a reply frame and must forward it to the peer.
static int sender(char frame, int frame_len, void privdata) {
printf("%.*s\n", frame_len, frame); // Print the JSON-RPC reply to stdout
return frame_len;
}
int main(void) {
jsonrpc_init(NULL, NULL);
// Call rpc.list
char request1[] = "{\"id\": 1, \"method\": \"rpc.list\"}";
jsonrpc_process(request1, strlen(request1), sender, NULL, NULL);
// Call non-existent method
char request2[] = "{\"id\": 1, \"method\": \"foo\"}";
jsonrpc_process(request2, strlen(request2), sender, NULL, NULL);
// Register our own function
char request3[] = "{\"id\": 2, \"method\": \"foo\",\"params\":[0,1.23]}";
jsonrpc_export("foo", foo);
jsonrpc_process(request3, strlen(request3), sender, NULL, (void *) "hi!");
return 0;
}
`
- -D MJSON_ENABLE_PRINT=0 disable emitting functionality, default: enabled-D MJSON_MAX_DEPTH=30
- define max object depth, default: 20-D MJSON_ENABLE_BASE64=0
- disable base64 parsing/printing, default: enabled-D MJSON_ENABLE_RPC=0
- disable RPC functionality, default: enabled-D MJSON_DYNBUF_CHUNK=256
- sets the allocation granularity of mjson_print_dynamic_buf-D MJSON_ENABLE_PRETTY=0
- disable mjson_pretty(), default: enabled-D MJSON_ENABLE_MERGE=0
- disable mjson_merge(), default: enabled-D MJSON_ENABLE_NEXT=0
- disable mjson_next(), default: enabled-D MJSON_REALLOC=my_realloc
- redefine realloc() used by mjson_print_dynamic_buf(), default: realloc
`c`
int mjson_find(const char s, int len, const char path, const char *tokptr, int toklen);
In a JSON string s, len, find an element by its JSONPATH path.tokptr
Save found element in , toklen.JSON_TOK_INVALID
If not found, return . If found, return one of:MJSON_TOK_STRING, MJSON_TOK_NUMBER, MJSON_TOK_TRUE, MJSON_TOK_FALSE,MJSON_TOK_NULL, MJSON_TOK_ARRAY, MJSON_TOK_OBJECT. If a searched key.
contains , [ or ] characters, they should be escaped by a backslash.
Example:
`c`
// s, len is a JSON string: {"foo": { "bar": [ 1, 2, 3] }, "b.az": true}
char *p;
int n;
assert(mjson_find(s, len, "$.foo.bar[1]", &p, &n) == MJSON_TOK_NUMBER);
assert(mjson_find(s, len, "$.b\\.az", &p, &n) == MJSON_TOK_TRUE);
assert(mjson_find(s, len, "$", &p, &n) == MJSON_TOK_OBJECT);
`c`
int mjson_get_number(const char s, int len, const char path, double *v);
In a JSON string s, len, find a number value by its JSONPATH path andv
store into . Return 0 if the value was not found, non-0 if found and stored.
Example:
`c`
// s, len is a JSON string: {"foo": { "bar": [ 1, 2, 3] }, "baz": true}
double v = 0;
mjson_get_number(s, len, "$.foo.bar[1]", &v); // v now holds 2
`c`
int mjson_get_bool(const char s, int len, const char path, int *v);
In a JSON string s, len, store value of a boolean by its JSONPATH pathv
into a variable . Return 0 if not found, non-0 otherwise. Example:
`c`
// s, len is a JSON string: {"foo": { "bar": [ 1, 2, 3] }, "baz": true}
bool v = mjson_get_bool(s, len, "$.baz", false); // Assigns to true
`c`
int mjson_get_string(const char s, int len, const char path, char *to, int sz);s
In a JSON string , len, find a string by its JSONPATH path and unescapeto
it into a buffer , sz with terminating \0.
If a string is not found, return -1.
If a string is found, return the length of unescaped string. Example:
`c`
// s, len is a JSON string [ "abc", "de\r\n" ]
char buf[100];
int n = mjson_get_string(s, len, "$[1]", buf, sizeof(buf)); // Assigns to 4
`c`
int mjson_get_hex(const char s, int len, const char path, char *to, int sz);
In a JSON string s, len, find a string by its JSONPATH path andto
hex decode it into a buffer , sz with terminating \0.Hello
If a string is not found, return -1.
If a string is found, return the length of decoded string.
The hex string should be lowercase, e.g. string is hex-encoded as"48656c6c6f". Example:
`c`
// s, len is a JSON string [ "48656c6c6f" ]
char buf[100];
int n = mjson_get_hex(s, len, "$[0]", buf, sizeof(buf)); // Assigns to 5
`c`
int mjson_get_base64(const char s, int len, const char path, char *to, int sz);
In a JSON string s, len, find a string by its JSONPATH path andto
base64 decode it into a buffer , sz with terminating \0.
If a string is not found, return 0.
If a string is found, return the length of decoded string. Example:
`c`
// s, len is a JSON string [ "MA==" ]
char buf[100];
int n = mjson_get_base64(s, len, "$[0]", buf, sizeof(buf)); // Assigns to 1
`c`
int mjson(const char s, int len, mjson_cb_t cb, void cbdata);
Parse JSON string s, len, calling callback cb for each token. This
is a low-level SAX API, intended for fancy stuff like pretty printing, etc.
`c`
int mjson_next(const char s, int n, int off, int koff, int klen, int voff,
int vlen, int vtype);
Assuming that JSON string s, n contains JSON object or JSON array,off
return the next key/value pair starting from offset .koff
key is returned as (key offset), klen (key length), value is returned as voff (value offset),vlen (value length), vtype (value type). Pointers could be NULL.koff
Return next offset. When iterating over the array, will hold valueklen
index inside an array, and will be 0. Therefore, if klen holds0, we're iterating over an array, otherwise over an object.
Note: initial offset should be 0.
Usage example:
`c
const char *s = "{\"a\":123,\"b\":[1,2,3,{\"c\":1}],\"d\":null}";
int koff, klen, voff, vlen, vtype, off;
for (off = 0; (off = mjson_next(s, strlen(s), off, &koff, &klen,
&voff, &vlen, &vtype)) != 0; ) {
printf("key: %.s, value: %.s\n", klen, s + koff, vlen, s + voff);
}
`
The emitting API is flexible and can print to anything: fixed buffer,
dynamic growing buffer, FILE *, network socket, etc etc. The printer function
gets the pointer to the buffer to print, and a user-specified data:
`c`
typedef int (mjson_print_fn_t)(const char buf, int len, void *userdata);
mjson library defines the following built-in printer functions:
`c
struct mjson_fixedbuf {
char *ptr;
int size, len;
};
int mjson_print_fixed_buf(const char ptr, int len, void userdata);
int mjson_print_file(const char ptr, int len, void userdata);
int mjson_print_dynamic_buf(const char ptr, int len, void userdata);
`
If you want to print to something else, for example to a network socket,
define your own printing function. If you want to see usage examples
for the built-in printing functions, see unit_test.c file.
`c`
int mjson_vprintf(mjson_print_fn_t, void , const char fmt, va_list ap);
int mjson_printf(mjson_print_fn_t, void , const char fmt, ...);
Print using printf()-like format string. Supported specifiers are:
- %Q print quoted escaped string. Expect NUL-terminated char *%.Q
- print quoted escaped string. Expect int, char %s
- print string as is. Expect NUL-terminated char *%.s
- print string as is. Expect int, char %g
- , print floating point number, precision is set to 6. Expect double%.*g
- , print floating point number with given precision. Expect int, double%d
- , %u print signed/unsigned integer. Expect int%ld
- , %lu print signed/unsigned long integer. Expect long%B
- print true or false. Expect int%V
- print quoted base64-encoded string. Expect int, char *%H
- print quoted hex-encoded string. Expect int, char *%M
- print using custom print function. Expect int ()(mjson_print_fn_t, void , va_list *)
The following example produces {"a":1, "b":[1234]} into thes
dynamically-growing string .
Note that the array is printed using a custom printer function:
`c
static int m_printer(mjson_print_fn_t fn, void fndata, va_list ap) {
int value = va_arg(*ap, int);
return mjson_printf(fn, fndata, "[%d]", value);
}
...
char *s = NULL;
mjson_printf(&mjson_print_dynamic_buf, &s, "{%Q:%d, %Q:%M}", "a", 1, "b", m_printer, 1234);
/ At this point s contains: {"a":1, "b":[1234]} /
free(s);
`
`c`
int mjson_snprintf(char buf, size_t len, const char fmt, ...);
A convenience function that prints into a given string.
`c`
char mjson_aprintf(const char fmt, ...);
A convenience function that prints into an allocated string. A returned
pointer must be free()-ed by a caller.
`c`
int mjson_pretty(const char s, int n, const char pad,
mjson_print_fn_t fn, void *userdata);
Pretty-print JSON string s, n using padding pad. If pad is "",
then a resulting string is terse one-line. Return length of the printed string.
`c`
int mjson_merge(const char s, int n, const char s2, int n2,
mjson_print_fn_t fn, void *fndata);
Merge JSON string s2,n2 into the original string s,n. Both stringsfn
are assumed to hold objects. The result is printed using ,fndata.
Return value: number of bytes printed.
In order to delete the key in the original string, set that key to nulls2
in the ,n2.
NOTE: both strings must not contain arrays, as merging arrays is not supported.
For the example, see unit_test.c :: test_rpc() function.
`c`
void jsonrpc_init(void (response_cb)(const char , int, void *),
void *response_cb_data);
Initialize JSON-RPC context. The sender() function must be providedsender()
by the caller, and it is responsible to send the prepared JSON-RPC
reply to the remote side - to the UART, or socket, or whatever.
The function receives the full frame to send, and the privdata
poitner.
The response_cb() function could be left NULL. If it is non-NULL, it willjsonrpc_call()
be called for all received responses generated by the .response_cb()
The function receives full response frame, and the privdata
pointer.
`c`
jsonrpc_process(const char frame, int frame_len, jsonrpc_sender_t fn, void fdata, void *userdata);
Parse JSON-RPC frame contained in frame, and invoke a registered handler.userdata
The pointer gets passed as r->userdata to the RPC handler.
`c`
#define jsonrpc_export(const char *name,
void (handler)(struct jsonrpc_request ));
Export JSON-RPC function. A function gets called by jsonrpc_process(),handler()
which parses an incoming frame and calls a registered handler.
A receives struct jsonrpc_request *. It could usejsonrpc_return_error() or jsonrpc_return_success() for returning the result.
NOTE: a name is a glob pattern that follows these rules:*
- matches 0 or more characters, excluding /?
- matches any character#
- matches 0 or more characters
- any other character matches itself
For example, after jsonrpc_export("Foo.*", my_func);,my_func
the server triggers on Foo.Bar, Foo.Baz, etc.
`c`
struct jsonrpc_request {
struct jsonrpc_ctx *ctx;
const char *params; // Points to the "params" in the request frame
int params_len; // Length of the "params"
const char *id; // Points to the "id" in the request frame
int id_len; // Length of the "id"
mjson_print_fn_t fn; // Printer function
void *fndata; // Printer function data
void *userdata; // userdata pointer passed to jsonrpc_process()
};
This structure gets passed to the method callback.
`c`
void jsonrpc_return_success(struct jsonrpc_request r, const char result_fmt, ...);
Return result from the method handler. NOTE: if the request frame ID
is not specified, this function does nothing.
`c`
void jsonrpc_return_error(struct jsonrpc_request r, int code, const char message, const char *data_fmt, ...);
Return error from the method handler. JSON-RPC error frame looks like this:
`json`
{"id":1, "error": {"code": -32602, "message": "Invalid params", "data": {"foo": "bar"}}}
The frame contains a error object with numeric code and string messagedata
keys, and an optional which can be arbitrary - a simple JSON type,data`, you can pass some extra information
or an array/object. In the optional
about the error, for example a faulty request.
NOTE: if the request frame ID
is not specified, this function does nothing.