{"id":13414465,"url":"https://github.com/cesanta/mjson","last_synced_at":"2025-10-09T23:37:51.610Z","repository":{"id":38540545,"uuid":"135180908","full_name":"cesanta/mjson","owner":"cesanta","description":"C/C++ JSON parser, emitter, JSON-RPC engine for embedded systems","archived":false,"fork":false,"pushed_at":"2024-04-23T15:37:47.000Z","size":376,"stargazers_count":401,"open_issues_count":23,"forks_count":80,"subscribers_count":26,"default_branch":"master","last_synced_at":"2024-07-31T21:53:01.051Z","etag":null,"topics":["c","embedded","json","json-rpc","mit"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cesanta.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-05-28T15:47:27.000Z","updated_at":"2024-07-30T21:02:59.000Z","dependencies_parsed_at":"2022-07-11T19:53:05.646Z","dependency_job_id":"aa760993-f670-4ccc-8347-2331ad174691","html_url":"https://github.com/cesanta/mjson","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cesanta%2Fmjson","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cesanta%2Fmjson/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cesanta%2Fmjson/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cesanta%2Fmjson/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cesanta","download_url":"https://codeload.github.com/cesanta/mjson/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":221509017,"owners_count":16834813,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["c","embedded","json","json-rpc","mit"],"created_at":"2024-07-30T21:00:21.795Z","updated_at":"2025-10-09T23:37:46.569Z","avatar_url":"https://github.com/cesanta.png","language":"C","funding_links":[],"categories":["Awesome Mongoose OS [![Awesome](https://awesome.re/badge.svg)](https://awesome.re)","Libraries"],"sub_categories":["Community Tutorials"],"readme":"# mjson - a JSON parser + emitter + JSON-RPC engine\n\n[![Build Status]( https://github.com/cesanta/mjson/workflows/build/badge.svg)](https://github.com/cesanta/mjson/actions)\n[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)\n[![Code Coverage](https://codecov.io/gh/cesanta/mjson/branch/master/graph/badge.svg)](https://codecov.io/gh/cesanta/mjson)\n\n# Features\n\n- Small, ~1k lines of code, embedded-friendly\n- No dependencies\n- State machine parser, no allocations, no recursion\n- High level API - fetch from JSON directly into C/C++ by\n    [jsonpath](https://github.com/json-path/JsonPath)\n- Low level SAX API\n- Flexible JSON generation API - print to buffer, file, socket, etc\n- JSON-RPC client/server. Connects any microcontroller online via https://vcon.io\n\n## Parsing example\n\n```c\nconst char *s = \"{\\\"a\\\":1,\\\"b\\\":[2,false]}\";  // {\"a\":1,\"b\":[2,false]}\n\ndouble val;                                       // Get `a` attribute\nif (mjson_get_number(s, strlen(s), \"$.a\", \u0026val))  // into C variable `val`\n  printf(\"a: %g\\n\", val);                         // a: 1\n\nconst char *buf;  // Get `b` sub-object\nint len;          // into C variables `buf,len`\nif (mjson_find(s, strlen(s), \"$.b\", \u0026buf, \u0026len))  // And print it\n  printf(\"%.*s\\n\", len, buf);                     // [2,false]\n\nint v;                                           // Extract `false`\nif (mjson_get_bool(s, strlen(s), \"$.b[1]\", \u0026v))  // into C variable `v`\n  printf(\"boolean: %d\\n\", v);                    // boolean: 0\n```\n\n## Printing example\n\n```c\n// Print into a statically allocated buffer\nchar buf[100];\nmjson_snprintf(buf, sizeof(buf), \"{%Q:%d}\", \"a\", 123);\nprintf(\"%s\\n\", buf);  // {\"a\":123}\n\n// Print into a dynamically allocated string\nchar *s = mjson_aprintf(\"{%Q:%g}\", \"a\", 3.1415);\nprintf(\"%s\\n\", s);  // {\"a\":3.1415}\nfree(s);            // Don't forget to free an allocated string\n```\n\n## JSON-RPC example\n\nIn the following example, we initialize JSON-RPC context, and call\na couple of JSON-RPC methods: a built-in `rpc.list` method which lists\nall registered methods, and our own `foo` method.\n\nThe `sender()` implementation just prints the reply to the standard output,\nbut in real life it should send a reply to the real remote peer - UART, socket,\nor whatever else.\n\n```c\n#include \"mjson.h\"\n\n// A custom RPC handler. Many handlers can be registered.\nstatic void foo(struct jsonrpc_request *r) {\n  double x;\n  mjson_get_number(r-\u003eparams, r-\u003eparams_len, \"$[1]\", \u0026x);\n  jsonrpc_return_success(r, \"{%Q:%g,%Q:%Q}\", \"x\", x, \"ud\", r-\u003euserdata);\n}\n\n// Sender function receives a reply frame and must forward it to the peer.\nstatic int sender(char *frame, int frame_len, void *privdata) {\n  printf(\"%.*s\\n\", frame_len, frame); // Print the JSON-RPC reply to stdout\n  return frame_len;\n}\n\nint main(void) {\n  jsonrpc_init(NULL, NULL);\n\n  // Call rpc.list\n  char request1[] = \"{\\\"id\\\": 1, \\\"method\\\": \\\"rpc.list\\\"}\";\n  jsonrpc_process(request1, strlen(request1), sender, NULL, NULL);\n\n  // Call non-existent method\n  char request2[] = \"{\\\"id\\\": 1, \\\"method\\\": \\\"foo\\\"}\";\n  jsonrpc_process(request2, strlen(request2), sender, NULL, NULL);\n\n  // Register our own function\n  char request3[] = \"{\\\"id\\\": 2, \\\"method\\\": \\\"foo\\\",\\\"params\\\":[0,1.23]}\";\n  jsonrpc_export(\"foo\", foo);\n  jsonrpc_process(request3, strlen(request3), sender, NULL, (void *) \"hi!\");\n\n  return 0;\n}\n```\n\n\n# Build options\n\n- `-D MJSON_ENABLE_PRINT=0` disable emitting functionality, default: enabled\n- `-D MJSON_MAX_DEPTH=30` define max object depth, default: 20\n- `-D MJSON_ENABLE_BASE64=0` disable base64 parsing/printing, default: enabled\n- `-D MJSON_ENABLE_RPC=0` disable RPC functionality, default: enabled\n- `-D MJSON_DYNBUF_CHUNK=256` sets the allocation granularity of `mjson_print_dynamic_buf`\n- `-D MJSON_ENABLE_PRETTY=0` disable `mjson_pretty()`, default: enabled\n- `-D MJSON_ENABLE_MERGE=0` disable `mjson_merge()`, default: enabled\n- `-D MJSON_ENABLE_NEXT=0` disable `mjson_next()`, default: enabled\n- `-D MJSON_REALLOC=my_realloc` redefine realloc() used by `mjson_print_dynamic_buf()`, default: realloc\n\n\n# Parsing API\n\n## mjson_find()\n\n```c\nint mjson_find(const char *s, int len, const char *path, const char **tokptr, int *toklen);\n```\n\nIn a JSON string `s`, `len`, find an element by its JSONPATH `path`.\nSave found element in `tokptr`, `toklen`.\nIf not found, return `JSON_TOK_INVALID`. If found, return one of:\n`MJSON_TOK_STRING`, `MJSON_TOK_NUMBER`, `MJSON_TOK_TRUE`, `MJSON_TOK_FALSE`,\n`MJSON_TOK_NULL`, `MJSON_TOK_ARRAY`, `MJSON_TOK_OBJECT`. If a searched key\ncontains `.`, `[` or `]` characters, they should be escaped by a backslash.\n\nExample:\n\n```c\n// s, len is a JSON string: {\"foo\": { \"bar\": [ 1, 2, 3] }, \"b.az\": true} \nchar *p;\nint n;\nassert(mjson_find(s, len, \"$.foo.bar[1]\", \u0026p, \u0026n) == MJSON_TOK_NUMBER);\nassert(mjson_find(s, len, \"$.b\\\\.az\", \u0026p, \u0026n) == MJSON_TOK_TRUE);\nassert(mjson_find(s, len, \"$\", \u0026p, \u0026n) == MJSON_TOK_OBJECT);\n```\n\n## mjson_get_number()\n\n```c\nint mjson_get_number(const char *s, int len, const char *path, double *v);\n```\n\nIn a JSON string `s`, `len`, find a number value by its JSONPATH `path` and\nstore into `v`. Return 0 if the value was not found, non-0 if found and stored.\nExample:\n\n```c\n// s, len is a JSON string: {\"foo\": { \"bar\": [ 1, 2, 3] }, \"baz\": true} \ndouble v = 0;\nmjson_get_number(s, len, \"$.foo.bar[1]\", \u0026v);  // v now holds 2\n```\n\n## mjson_get_bool()\n\n```c\nint mjson_get_bool(const char *s, int len, const char *path, int *v);\n```\n\nIn a JSON string `s`, `len`, store value of a boolean by its JSONPATH `path`\ninto a variable `v`. Return 0 if not found, non-0 otherwise. Example:\n\n```c\n// s, len is a JSON string: {\"foo\": { \"bar\": [ 1, 2, 3] }, \"baz\": true} \nbool v = mjson_get_bool(s, len, \"$.baz\", false);   // Assigns to true\n```\n\n## mjson_get_string()\n\n```c\nint mjson_get_string(const char *s, int len, const char *path, char *to, int sz);\n```\nIn a JSON string `s`, `len`, find a string by its JSONPATH `path` and unescape\nit into a buffer `to`, `sz` with terminating `\\0`.\nIf a string is not found, return -1.\nIf a string is found, return the length of unescaped string. Example:\n\n```c\n// s, len is a JSON string [ \"abc\", \"de\\r\\n\" ]\nchar buf[100];\nint n = mjson_get_string(s, len, \"$[1]\", buf, sizeof(buf));  // Assigns to 4\n```\n\n## mjson_get_hex()\n\n```c\nint mjson_get_hex(const char *s, int len, const char *path, char *to, int sz);\n```\n\nIn a JSON string `s`, `len`, find a string by its JSONPATH `path` and\nhex decode it into a buffer `to`, `sz` with terminating `\\0`.\nIf a string is not found, return -1.\nIf a string is found, return the length of decoded string.\nThe hex string should be lowercase, e.g. string `Hello` is hex-encoded as\n`\"48656c6c6f\"`.  Example:\n\n```c\n// s, len is a JSON string [ \"48656c6c6f\" ]\nchar buf[100];\nint n = mjson_get_hex(s, len, \"$[0]\", buf, sizeof(buf));  // Assigns to 5\n```\n\n\n\n## mjson_get_base64()\n\n```c\nint mjson_get_base64(const char *s, int len, const char *path, char *to, int sz);\n```\n\nIn a JSON string `s`, `len`, find a string by its JSONPATH `path` and\nbase64 decode it into a buffer `to`, `sz` with terminating `\\0`.\nIf a string is not found, return 0.\nIf a string is found, return the length of decoded string. Example:\n\n```c\n// s, len is a JSON string [ \"MA==\" ]\nchar buf[100];\nint n = mjson_get_base64(s, len, \"$[0]\", buf, sizeof(buf));  // Assigns to 1\n```\n\n\n## mjson()\n\n```c\nint mjson(const char *s, int len, mjson_cb_t cb, void *cbdata);\n```\n\nParse JSON string `s`, `len`, calling callback `cb` for each token. This\nis a low-level SAX API, intended for fancy stuff like pretty printing, etc.\n\n\n## mjson_next()\n\n```c\nint mjson_next(const char *s, int n, int off, int *koff, int *klen, int *voff,\n               int *vlen, int *vtype);\n```\n\nAssuming that JSON string `s`, `n` contains JSON object or JSON array,\nreturn the next key/value pair starting from offset `off`.\nkey is returned as  `koff` (key offset), `klen` (key length), value is returned as `voff` (value offset),\n`vlen` (value length), `vtype` (value type). Pointers could be NULL.\nReturn next offset. When iterating over the array, `koff` will hold value\nindex inside an array, and `klen` will be `0`. Therefore, if `klen` holds\n`0`, we're iterating over an array, otherwise over an object.\nNote: initial offset should be 0.\n\nUsage example:\n\n```c\nconst char *s = \"{\\\"a\\\":123,\\\"b\\\":[1,2,3,{\\\"c\\\":1}],\\\"d\\\":null}\";\nint koff, klen, voff, vlen, vtype, off;\n\nfor (off = 0; (off = mjson_next(s, strlen(s), off, \u0026koff, \u0026klen,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\u0026voff, \u0026vlen, \u0026vtype)) != 0; ) {\n\tprintf(\"key: %.*s, value: %.*s\\n\", klen, s + koff, vlen, s + voff);\n}\n```\n\n\n# Emitting API\n\n\nThe emitting API is flexible and can print to anything: fixed buffer,\ndynamic growing buffer, FILE *, network socket, etc etc. The printer function\ngets the pointer to the buffer to print, and a user-specified data:\n\n```c\ntypedef int (*mjson_print_fn_t)(const char *buf, int len, void *userdata);\n```\n\nmjson library defines the following built-in printer functions:\n\n```c\nstruct mjson_fixedbuf {\n  char *ptr;\n  int size, len;\n};\nint mjson_print_fixed_buf(const char *ptr, int len, void *userdata);\n\nint mjson_print_file(const char *ptr, int len, void *userdata);\nint mjson_print_dynamic_buf(const char *ptr, int len, void *userdata);\n```\n\nIf you want to print to something else, for example to a network socket,\ndefine your own printing function. If you want to see usage examples\nfor the built-in printing functions, see `unit_test.c` file.\n\n## mjson_printf()\n\n```c\nint mjson_vprintf(mjson_print_fn_t, void *, const char *fmt, va_list ap);\nint mjson_printf(mjson_print_fn_t, void *, const char *fmt, ...);\n```\n\nPrint using `printf()`-like format string. Supported specifiers are:\n\n- `%Q` print quoted escaped string. Expect NUL-terminated `char *`\n- `%.*Q` print quoted escaped string. Expect `int, char *`\n- `%s` print string as is. Expect NUL-terminated `char *`\n- `%.*s` print string as is. Expect `int, char *`\n- `%g`, print floating point number, precision is set to 6. Expect `double`\n- `%.*g`, print floating point number with given precision. Expect `int, double`\n- `%d`, `%u` print signed/unsigned integer. Expect `int`\n- `%ld`, `%lu` print signed/unsigned long integer. Expect `long`\n- `%B` print `true` or `false`. Expect `int`\n- `%V` print quoted base64-encoded string. Expect `int, char *`\n- `%H` print quoted hex-encoded string. Expect `int, char *`\n- `%M` print using custom print function. Expect `int (*)(mjson_print_fn_t, void *, va_list *)`\n\nThe following example produces `{\"a\":1, \"b\":[1234]}` into the\ndynamically-growing string `s`.\nNote that the array is printed using a custom printer function:\n\n```c\nstatic int m_printer(mjson_print_fn_t fn, void *fndata, va_list *ap) {\n  int value = va_arg(*ap, int);\n  return mjson_printf(fn, fndata, \"[%d]\", value);\n}\n\n...\nchar *s = NULL;\nmjson_printf(\u0026mjson_print_dynamic_buf, \u0026s, \"{%Q:%d, %Q:%M}\", \"a\", 1, \"b\", m_printer, 1234);\n/* At this point `s` contains: {\"a\":1, \"b\":[1234]}  */\nfree(s);\n```\n\n## mjson_snprintf()\n\n```c\nint mjson_snprintf(char *buf, size_t len, const char *fmt, ...);\n```\n\nA convenience function that prints into a given string.\n\n## mjson_aprintf()\n\n```c\nchar *mjson_aprintf(const char *fmt, ...);\n```\n\nA convenience function that prints into an allocated string. A returned\npointer must be `free()`-ed by a caller.\n\n## mjson_pretty()\n\n```c\nint mjson_pretty(const char *s, int n, const char *pad,\n                 mjson_print_fn_t fn, void *userdata);\n```\n\nPretty-print JSON string `s`, `n` using padding `pad`. If `pad` is `\"\"`,\nthen a resulting string is terse one-line. Return length of the printed string.\n\n\n## mjson_merge()\n\n```c\nint mjson_merge(const char *s, int n, const char *s2, int n2,\n                mjson_print_fn_t fn, void *fndata);\n```\n\nMerge JSON string `s2`,`n2` into the original string `s`,`n`. Both strings\nare assumed to hold objects. The result is printed using `fn`,`fndata`.\nReturn value: number of bytes printed.\n\nIn order to delete the key in the original string, set that key to `null`\nin the `s2`,`n2`.\nNOTE: both strings must not contain arrays, as merging arrays is not supported.\n\n\n# JSON-RPC API\n\nFor the example, see `unit_test.c :: test_rpc()` function.\n\n## jsonrpc_init\n\n```c\nvoid jsonrpc_init(void (*response_cb)(const char *, int, void *),\n                  void *response_cb_data);\n```\n\nInitialize JSON-RPC context. The `sender()` function must be provided\nby the caller, and it is responsible to send the prepared JSON-RPC\nreply to the remote side - to the UART, or socket, or whatever.\nThe `sender()` function receives the full frame to send, and the `privdata`\npoitner.\n\nThe `response_cb()` function could be left NULL. If it is non-NULL, it will\nbe called for all received responses generated by the `jsonrpc_call()`.\nThe `response_cb()` function receives full response frame, and the `privdata`\npointer.\n\n## jsonrpc_process\n\n```c\njsonrpc_process(const char *frame, int frame_len, jsonrpc_sender_t fn, void *fdata, void *userdata);\n```\n\nParse JSON-RPC frame contained in `frame`, and invoke a registered handler.\nThe `userdata` pointer gets passed as `r-\u003euserdata` to the RPC handler.\n\n\n## jsonrpc_export\n\n```c\n#define jsonrpc_export(const char *name,\n                       void (*handler)(struct jsonrpc_request *));\n```\n\nExport JSON-RPC function. A function gets called by `jsonrpc_process()`,\nwhich parses an incoming frame and calls a registered handler.\nA `handler()` receives `struct jsonrpc_request *`. It could use\n`jsonrpc_return_error()` or `jsonrpc_return_success()` for returning the result.\n\nNOTE: a `name` is a glob pattern that follows these rules:\n- `*` matches 0 or more characters, excluding `/`\n- `?` matches any character\n- `#` matches 0 or more characters\n- any other character matches itself\n\nFor example, after `jsonrpc_export(\"Foo.*\", my_func);`,\nthe server triggers `my_func` on `Foo.Bar`, `Foo.Baz`, etc.\n\n## struct jsonrpc_request\n\n```c\nstruct jsonrpc_request {\n  struct jsonrpc_ctx *ctx;\n  const char *params;     // Points to the \"params\" in the request frame\n  int params_len;         // Length of the \"params\"\n  const char *id;         // Points to the \"id\" in the request frame\n  int id_len;             // Length of the \"id\"\n  mjson_print_fn_t fn;    // Printer function\n  void *fndata;           // Printer function data\n  void *userdata;         // userdata pointer passed to jsonrpc_process()\n};\n```\n\nThis structure gets passed to the method callback.\n\n## jsonrpc_return_success\n\n```c\nvoid jsonrpc_return_success(struct jsonrpc_request *r, const char *result_fmt, ...);\n```\n\nReturn result from the method handler. NOTE: if the request frame ID\nis not specified, this function does nothing.\n\n## jsonrpc_return_error\n\n```c\nvoid jsonrpc_return_error(struct jsonrpc_request *r, int code, const char *message, const char *data_fmt, ...);\n```\n\nReturn error from the method handler. JSON-RPC error frame looks like this:\n\n```json\n{\"id\":1, \"error\": {\"code\": -32602, \"message\": \"Invalid params\", \"data\": {\"foo\": \"bar\"}}}\n```\n\nThe frame contains a `error` object with numeric `code` and string `message`\nkeys, and an optional `data` which can be arbitrary - a simple JSON type,\nor an array/object. In the optional `data`, you can pass some extra information\nabout the error, for example a faulty request.\n\nNOTE: if the request frame ID\nis not specified, this function does nothing.\n\n\n## JSON-RPC Arduino example\n\n```c\n#include \"mjson.h\"\n\n// Gets called by the RPC engine to send a reply frame\nstatic int sender(const char *frame, int frame_len, void *privdata) {\n  return Serial.write(frame, frame_len);\n}\n\n// RPC handler for \"Sum\". Expect an array of two integers in \"params\"\nstatic void sum(struct jsonrpc_request *r) {\n  int a = mjson_get_number(r-\u003eparams, r-\u003eparams_len, \"$[0]\", 0);\n  int b = mjson_get_number(r-\u003eparams, r-\u003eparams_len, \"$[1]\", 0);\n  jsonrpc_return_success(r, \"%d\", a + b);\n}\n\nvoid setup() {\n  jsonrpc_init(NULL, NULL);     // Initialise the library\n  jsonrpc_export(\"Sum\", sum);   // Export \"Sum\" function\n  Serial.begin(115200);         // Setup serial port\n}\n\nstatic void handle_serial_input(unsigned char ch) {\n  static char buf[256];  // Buffer that holds incoming frame\n  static size_t len;     // Current frame length\n\n  if (len \u003e= sizeof(buf)) len = 0;  // Handle overflow - just reset\n  buf[len++] = ch;                  // Append to the buffer\n  if (ch == '\\n') {                 // On new line, parse frame\n    jsonrpc_process(buf, len, sender, NULL, NULL);\n    len = 0;\n  }\n}\n\nvoid loop() {\n  char buf[800];\n  if (Serial.available() \u003e 0) {\n    int len = Serial.readBytes(buf, sizeof(buf));\n    jsonrpc_process(buf, len, sender, NULL, NULL);\n  }\n}\n```\n\nWhen this sketch is compiled and flashed on an Arduino\nboard, start Arduino Serial Monitor, type\n`{\"id\": 1, \"method\": \"Sum\", \"params\": [2,3]}` and hit enter. You should\nsee an answer frame:\n\n![](example/rpc1.png)\n\n# Example - connect Arduino Uno to AWS IoT device shadow\n\n[![](http://i3.ytimg.com/vi/od1rsIrvwrM/hqdefault.jpg)](https://www.youtube.com/watch?v=od1rsIrvwrM)\n\nSee https://vcon.io for more information.\n\n# Contact\n\nPlease visit https://vcon.io/contact.html\n\n# See also\n- [Mongoose Web Server Library](https://mongoose.ws/) - a robust, open-source solution licensed under GPLv2, designed to seamlessly integrate web server functionality into your embedded devices. \n- With complementary [Mongoose Wizard](https://mongoose.ws/wizard/) - a no-code visual tool that enables rapid WebUI creation without the need for frontend expertise.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcesanta%2Fmjson","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcesanta%2Fmjson","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcesanta%2Fmjson/lists"}