402 lines
10 KiB
C
402 lines
10 KiB
C
#include "json.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define JSMN_ERROR_NOMEM -1
|
|
#define JSMN_ERROR_INVAL -2
|
|
#define JSMN_ERROR_PART -3
|
|
|
|
static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens, size_t num_tokens) {
|
|
jsmntok_t *tok;
|
|
|
|
if (parser->toknext >= num_tokens) {
|
|
return NULL;
|
|
}
|
|
|
|
tok = &tokens[parser->toknext++];
|
|
tok->start = -1;
|
|
tok->end = -1;
|
|
tok->size = 0;
|
|
tok->parent = -1;
|
|
tok->type = JSMN_UNDEFINED;
|
|
return tok;
|
|
}
|
|
|
|
static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, int start, int end) {
|
|
token->type = type;
|
|
token->start = start;
|
|
token->end = end;
|
|
token->size = 0;
|
|
}
|
|
|
|
void jsmn_init(jsmn_parser *parser) {
|
|
parser->pos = 0;
|
|
parser->toknext = 0;
|
|
parser->toksuper = -1;
|
|
}
|
|
|
|
static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, size_t num_tokens) {
|
|
int start = (int)parser->pos;
|
|
|
|
for (; parser->pos < len; parser->pos++) {
|
|
char c = js[parser->pos];
|
|
if (c == ':' || c == '\t' || c == '\r' || c == '\n' || c == ' ' || c == ',' || c == ']' || c == '}') {
|
|
jsmntok_t *token = jsmn_alloc_token(parser, tokens, num_tokens);
|
|
if (token == NULL) {
|
|
parser->pos = (unsigned int)start;
|
|
return JSMN_ERROR_NOMEM;
|
|
}
|
|
jsmn_fill_token(token, JSMN_PRIMITIVE, start, (int)parser->pos);
|
|
token->parent = parser->toksuper;
|
|
parser->pos--;
|
|
return 0;
|
|
}
|
|
|
|
if (c < 32 || c >= 127) {
|
|
parser->pos = (unsigned int)start;
|
|
return JSMN_ERROR_INVAL;
|
|
}
|
|
}
|
|
|
|
parser->pos = (unsigned int)start;
|
|
return JSMN_ERROR_PART;
|
|
}
|
|
|
|
static int jsmn_parse_string(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, size_t num_tokens) {
|
|
int start = (int)parser->pos;
|
|
|
|
parser->pos++;
|
|
|
|
for (; parser->pos < len; parser->pos++) {
|
|
char c = js[parser->pos];
|
|
|
|
if (c == '"') {
|
|
jsmntok_t *token = jsmn_alloc_token(parser, tokens, num_tokens);
|
|
if (token == NULL) {
|
|
parser->pos = (unsigned int)start;
|
|
return JSMN_ERROR_NOMEM;
|
|
}
|
|
jsmn_fill_token(token, JSMN_STRING, start + 1, (int)parser->pos);
|
|
token->parent = parser->toksuper;
|
|
return 0;
|
|
}
|
|
|
|
if (c == '\\' && parser->pos + 1 < len) {
|
|
char esc = js[parser->pos + 1];
|
|
|
|
if (esc == '"' || esc == '/' || esc == '\\' || esc == 'b' || esc == 'f' || esc == 'r' || esc == 'n' || esc == 't') {
|
|
parser->pos++;
|
|
continue;
|
|
}
|
|
|
|
if (esc == 'u') {
|
|
int i;
|
|
parser->pos++;
|
|
for (i = 0; i < 4 && parser->pos + 1 < len; i++) {
|
|
char hc = js[parser->pos + 1];
|
|
if (!((hc >= '0' && hc <= '9') || (hc >= 'A' && hc <= 'F') || (hc >= 'a' && hc <= 'f'))) {
|
|
parser->pos = (unsigned int)start;
|
|
return JSMN_ERROR_INVAL;
|
|
}
|
|
parser->pos++;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
parser->pos = (unsigned int)start;
|
|
return JSMN_ERROR_INVAL;
|
|
}
|
|
}
|
|
|
|
parser->pos = (unsigned int)start;
|
|
return JSMN_ERROR_PART;
|
|
}
|
|
|
|
int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, unsigned int num_tokens) {
|
|
int r;
|
|
unsigned int i;
|
|
|
|
for (; parser->pos < len; parser->pos++) {
|
|
char c = js[parser->pos];
|
|
|
|
switch (c) {
|
|
case '{':
|
|
case '[': {
|
|
jsmntype_t type = (c == '{') ? JSMN_OBJECT : JSMN_ARRAY;
|
|
jsmntok_t *token = jsmn_alloc_token(parser, tokens, num_tokens);
|
|
|
|
if (token == NULL) {
|
|
return JSMN_ERROR_NOMEM;
|
|
}
|
|
|
|
if (parser->toksuper != -1) {
|
|
tokens[parser->toksuper].size++;
|
|
token->parent = parser->toksuper;
|
|
}
|
|
|
|
jsmn_fill_token(token, type, (int)parser->pos, -1);
|
|
parser->toksuper = (int)(parser->toknext - 1);
|
|
break;
|
|
}
|
|
|
|
case '}':
|
|
case ']': {
|
|
jsmntype_t type = (c == '}') ? JSMN_OBJECT : JSMN_ARRAY;
|
|
|
|
for (i = parser->toknext; i > 0; i--) {
|
|
jsmntok_t *token = &tokens[i - 1];
|
|
if (token->start != -1 && token->end == -1) {
|
|
if (token->type != type) {
|
|
return JSMN_ERROR_INVAL;
|
|
}
|
|
token->end = (int)parser->pos + 1;
|
|
parser->toksuper = token->parent;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == 0) {
|
|
return JSMN_ERROR_INVAL;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case '"':
|
|
r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
if (parser->toksuper != -1) {
|
|
tokens[parser->toksuper].size++;
|
|
}
|
|
break;
|
|
|
|
case '\t':
|
|
case '\r':
|
|
case '\n':
|
|
case ' ':
|
|
case ':':
|
|
case ',':
|
|
break;
|
|
|
|
default:
|
|
r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
if (parser->toksuper != -1) {
|
|
tokens[parser->toksuper].size++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (i = parser->toknext; i > 0; i--) {
|
|
if (tokens[i - 1].start != -1 && tokens[i - 1].end == -1) {
|
|
return JSMN_ERROR_PART;
|
|
}
|
|
}
|
|
|
|
return (int)parser->toknext;
|
|
}
|
|
|
|
int json_doc_parse(JsonDoc *doc, const char *json) {
|
|
jsmn_parser p;
|
|
int r;
|
|
int cap = 1024;
|
|
|
|
doc->json = json;
|
|
doc->tokens = NULL;
|
|
doc->token_count = 0;
|
|
|
|
while (1) {
|
|
doc->tokens = (jsmntok_t *)malloc((size_t)cap * sizeof(jsmntok_t));
|
|
if (doc->tokens == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
jsmn_init(&p);
|
|
r = jsmn_parse(&p, json, strlen(json), doc->tokens, (unsigned int)cap);
|
|
if (r >= 0) {
|
|
doc->token_count = r;
|
|
return 1;
|
|
}
|
|
|
|
free(doc->tokens);
|
|
doc->tokens = NULL;
|
|
|
|
if (r == JSMN_ERROR_NOMEM) {
|
|
cap *= 2;
|
|
if (cap > (1 << 21)) {
|
|
return 0;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void json_doc_free(JsonDoc *doc) {
|
|
free(doc->tokens);
|
|
doc->tokens = NULL;
|
|
doc->token_count = 0;
|
|
doc->json = NULL;
|
|
}
|
|
|
|
int json_token_streq(const JsonDoc *doc, int tok_idx, const char *s) {
|
|
const jsmntok_t *tok;
|
|
size_t len;
|
|
|
|
if (tok_idx < 0 || tok_idx >= doc->token_count) {
|
|
return 0;
|
|
}
|
|
|
|
tok = &doc->tokens[tok_idx];
|
|
if (tok->type != JSMN_STRING && tok->type != JSMN_PRIMITIVE) {
|
|
return 0;
|
|
}
|
|
|
|
len = (size_t)(tok->end - tok->start);
|
|
if (len != strlen(s)) {
|
|
return 0;
|
|
}
|
|
|
|
return strncmp(doc->json + tok->start, s, len) == 0;
|
|
}
|
|
|
|
int json_skip_token(const JsonDoc *doc, int tok_idx) {
|
|
int i;
|
|
int next;
|
|
|
|
if (tok_idx < 0 || tok_idx >= doc->token_count) {
|
|
return -1;
|
|
}
|
|
|
|
if (doc->tokens[tok_idx].type == JSMN_STRING || doc->tokens[tok_idx].type == JSMN_PRIMITIVE) {
|
|
return tok_idx + 1;
|
|
}
|
|
|
|
if (doc->tokens[tok_idx].type == JSMN_ARRAY) {
|
|
next = tok_idx + 1;
|
|
for (i = 0; i < doc->tokens[tok_idx].size; i++) {
|
|
next = json_skip_token(doc, next);
|
|
if (next < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
return next;
|
|
}
|
|
|
|
if (doc->tokens[tok_idx].type == JSMN_OBJECT) {
|
|
int pairs = doc->tokens[tok_idx].size / 2;
|
|
next = tok_idx + 1;
|
|
for (i = 0; i < pairs; i++) {
|
|
next = json_skip_token(doc, next);
|
|
if (next < 0) {
|
|
return -1;
|
|
}
|
|
next = json_skip_token(doc, next);
|
|
if (next < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
return next;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int json_object_get(const JsonDoc *doc, int obj_idx, const char *key) {
|
|
int i;
|
|
int cur;
|
|
int pairs;
|
|
|
|
if (obj_idx < 0 || obj_idx >= doc->token_count) {
|
|
return -1;
|
|
}
|
|
|
|
if (doc->tokens[obj_idx].type != JSMN_OBJECT) {
|
|
return -1;
|
|
}
|
|
|
|
pairs = doc->tokens[obj_idx].size / 2;
|
|
cur = obj_idx + 1;
|
|
for (i = 0; i < pairs; i++) {
|
|
int key_idx = cur;
|
|
int value_idx;
|
|
|
|
cur = json_skip_token(doc, cur);
|
|
if (cur < 0) {
|
|
return -1;
|
|
}
|
|
|
|
value_idx = cur;
|
|
cur = json_skip_token(doc, cur);
|
|
if (cur < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (json_token_streq(doc, key_idx, key)) {
|
|
return value_idx;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int json_array_get(const JsonDoc *doc, int arr_idx, int elem_index) {
|
|
int i;
|
|
int cur;
|
|
|
|
if (arr_idx < 0 || arr_idx >= doc->token_count) {
|
|
return -1;
|
|
}
|
|
|
|
if (doc->tokens[arr_idx].type != JSMN_ARRAY) {
|
|
return -1;
|
|
}
|
|
|
|
if (elem_index < 0 || elem_index >= doc->tokens[arr_idx].size) {
|
|
return -1;
|
|
}
|
|
|
|
cur = arr_idx + 1;
|
|
for (i = 0; i < elem_index; i++) {
|
|
cur = json_skip_token(doc, cur);
|
|
if (cur < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return cur;
|
|
}
|
|
|
|
char *json_token_strdup(const JsonDoc *doc, int tok_idx) {
|
|
int start;
|
|
int end;
|
|
size_t len;
|
|
char *out;
|
|
|
|
if (tok_idx < 0 || tok_idx >= doc->token_count) {
|
|
return NULL;
|
|
}
|
|
|
|
start = doc->tokens[tok_idx].start;
|
|
end = doc->tokens[tok_idx].end;
|
|
if (start < 0 || end < start) {
|
|
return NULL;
|
|
}
|
|
|
|
len = (size_t)(end - start);
|
|
out = (char *)malloc(len + 1);
|
|
if (out == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
memcpy(out, doc->json + start, len);
|
|
out[len] = '\0';
|
|
return out;
|
|
}
|