Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/cesanta/mjson
C/C++ JSON parser, emitter, JSON-RPC engine for embedded systems
https://github.com/cesanta/mjson
c embedded json json-rpc mit
Last synced: 3 months ago
JSON representation
C/C++ JSON parser, emitter, JSON-RPC engine for embedded systems
- Host: GitHub
- URL: https://github.com/cesanta/mjson
- Owner: cesanta
- License: mit
- Created: 2018-05-28T15:47:27.000Z (over 6 years ago)
- Default Branch: master
- Last Pushed: 2024-04-23T15:37:47.000Z (9 months ago)
- Last Synced: 2024-07-31T21:53:01.051Z (6 months ago)
- Topics: c, embedded, json, json-rpc, mit
- Language: C
- Homepage:
- Size: 367 KB
- Stars: 401
- Watchers: 26
- Forks: 80
- Open Issues: 23
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-mongoose-os - mJSON - C/C++ JSON parser, emitter, JSON-RPC engine for embedded systems (Awesome Mongoose OS [![Awesome](https://awesome.re/badge.svg)](https://awesome.re) / Community Tutorials)
- awesome-json-rpc - cesanta/mjson - C/C++ JSON parser, emitter, JSON-RPC engine for embedded systems (Libraries)
README
# mjson - a JSON parser + emitter + JSON-RPC engine
[![Build Status]( https://github.com/cesanta/mjson/workflows/build/badge.svg)](https://github.com/cesanta/mjson/actions)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
[![Code Coverage](https://codecov.io/gh/cesanta/mjson/branch/master/graph/badge.svg)](https://codecov.io/gh/cesanta/mjson)# Features
- 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](https://github.com/json-path/JsonPath)
- Low level SAX API
- Flexible JSON generation API - print to buffer, file, socket, etc
- JSON-RPC client/server. Connects any microcontroller online via https://vcon.io## Parsing example
```c
const char *s = "{\"a\":1,\"b\":[2,false]}"; // {"a":1,"b":[2,false]}double val; // Get `a` attribute
if (mjson_get_number(s, strlen(s), "$.a", &val)) // into C variable `val`
printf("a: %g\n", val); // a: 1const char *buf; // Get `b` sub-object
int len; // into C variables `buf,len`
if (mjson_find(s, strlen(s), "$.b", &buf, &len)) // And print it
printf("%.*s\n", len, buf); // [2,false]int v; // Extract `false`
if (mjson_get_bool(s, strlen(s), "$.b[1]", &v)) // into C variable `v`
printf("boolean: %d\n", v); // boolean: 0
```## Printing example
```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
```## JSON-RPC example
In the following example, we initialize JSON-RPC context, and call
a couple of JSON-RPC methods: a built-in `rpc.list` method which lists
all registered methods, and our own `foo` 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;
}
```# Build options
- `-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# Parsing API
## mjson_find()
```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`.
Save found element in `tokptr`, `toklen`.
If not found, return `JSON_TOK_INVALID`. 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);
```## mjson_get_number()
```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` and
store into `v`. 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
```## mjson_get_bool()
```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 `path`
into a variable `v`. 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
```## mjson_get_string()
```c
int mjson_get_string(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` and unescape
it into a buffer `to`, `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
```## mjson_get_hex()
```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` and
hex decode it into a buffer `to`, `sz` with terminating `\0`.
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 `Hello` 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
```## mjson_get_base64()
```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` and
base64 decode it into a buffer `to`, `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
```## mjson()
```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.## mjson_next()
```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,
return the next key/value pair starting from offset `off`.
key is returned as `koff` (key offset), `klen` (key length), value is returned as `voff` (value offset),
`vlen` (value length), `vtype` (value type). Pointers could be NULL.
Return next offset. When iterating over the array, `koff` will hold value
index inside an array, and `klen` will be `0`. Therefore, if `klen` holds
`0`, 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);
}
```# Emitting API
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.## mjson_printf()
```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 the
dynamically-growing string `s`.
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);
```## mjson_snprintf()
```c
int mjson_snprintf(char *buf, size_t len, const char *fmt, ...);
```A convenience function that prints into a given string.
## mjson_aprintf()
```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.## mjson_pretty()
```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.## mjson_merge()
```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 strings
are assumed to hold objects. The result is printed using `fn`,`fndata`.
Return value: number of bytes printed.In order to delete the key in the original string, set that key to `null`
in the `s2`,`n2`.
NOTE: both strings must not contain arrays, as merging arrays is not supported.# JSON-RPC API
For the example, see `unit_test.c :: test_rpc()` function.
## jsonrpc_init
```c
void jsonrpc_init(void (*response_cb)(const char *, int, void *),
void *response_cb_data);
```Initialize JSON-RPC context. The `sender()` function must be provided
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 `sender()` 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 will
be called for all received responses generated by the `jsonrpc_call()`.
The `response_cb()` function receives full response frame, and the `privdata`
pointer.## jsonrpc_process
```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.
The `userdata` pointer gets passed as `r->userdata` to the RPC handler.## jsonrpc_export
```c
#define jsonrpc_export(const char *name,
void (*handler)(struct jsonrpc_request *));
```Export JSON-RPC function. A function gets called by `jsonrpc_process()`,
which parses an incoming frame and calls a registered handler.
A `handler()` receives `struct jsonrpc_request *`. It could use
`jsonrpc_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 itselfFor example, after `jsonrpc_export("Foo.*", my_func);`,
the server triggers `my_func` on `Foo.Bar`, `Foo.Baz`, etc.## struct jsonrpc_request
```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.
## jsonrpc_return_success
```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.## jsonrpc_return_error
```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 `message`
keys, and an optional `data` which can be arbitrary - a simple JSON type,
or an array/object. In the optional `data`, you can pass some extra information
about the error, for example a faulty request.NOTE: if the request frame ID
is not specified, this function does nothing.## JSON-RPC Arduino example
```c
#include "mjson.h"// Gets called by the RPC engine to send a reply frame
static int sender(const char *frame, int frame_len, void *privdata) {
return Serial.write(frame, frame_len);
}// RPC handler for "Sum". Expect an array of two integers in "params"
static void sum(struct jsonrpc_request *r) {
int a = mjson_get_number(r->params, r->params_len, "$[0]", 0);
int b = mjson_get_number(r->params, r->params_len, "$[1]", 0);
jsonrpc_return_success(r, "%d", a + b);
}void setup() {
jsonrpc_init(NULL, NULL); // Initialise the library
jsonrpc_export("Sum", sum); // Export "Sum" function
Serial.begin(115200); // Setup serial port
}static void handle_serial_input(unsigned char ch) {
static char buf[256]; // Buffer that holds incoming frame
static size_t len; // Current frame lengthif (len >= sizeof(buf)) len = 0; // Handle overflow - just reset
buf[len++] = ch; // Append to the buffer
if (ch == '\n') { // On new line, parse frame
jsonrpc_process(buf, len, sender, NULL, NULL);
len = 0;
}
}void loop() {
char buf[800];
if (Serial.available() > 0) {
int len = Serial.readBytes(buf, sizeof(buf));
jsonrpc_process(buf, len, sender, NULL, NULL);
}
}
```When this sketch is compiled and flashed on an Arduino
board, start Arduino Serial Monitor, type
`{"id": 1, "method": "Sum", "params": [2,3]}` and hit enter. You should
see an answer frame:![](example/rpc1.png)
# Example - connect Arduino Uno to AWS IoT device shadow
[![](http://i3.ytimg.com/vi/od1rsIrvwrM/hqdefault.jpg)](https://www.youtube.com/watch?v=od1rsIrvwrM)
See https://vcon.io for more information.
# Contact
Please visit https://vcon.io/contact.html