From 20b26406ff18b1b29c3d17b9633b93ddcdf2c303 Mon Sep 17 00:00:00 2001 From: Maxim Menshikov Date: Wed, 1 May 2019 09:33:36 +0300 Subject: [PATCH 1/4] Add helpers to get tokens by key/index, to get total count of subtokens in a token --- jsmn.h | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/jsmn.h b/jsmn.h index b95368a2..5f08891a 100644 --- a/jsmn.h +++ b/jsmn.h @@ -459,6 +459,113 @@ JSMN_API void jsmn_init(jsmn_parser *parser) { parser->toksuper = -1; } +/** + * Get the size (in tokens) consumed by token and its children. + */ +JSMN_API size_t jsmn_get_total_size(jsmntok_t *token) { + unsigned int i, j; + jsmntok_t *key; + + if (token->type == JSMN_PRIMITIVE) { + return 1; + } else if (token->type == JSMN_STRING) { + return 1; + } else if (token->type == JSMN_OBJECT) { + j = 0; + for (i = 0; i < token->size; i++) { + key = token + 1 + j; + j += jsmn_get_total_size(key); + if (key->size > 0) { + j += jsmn_get_total_size(token + 1 + j); + } + } + return j + 1; + } else if (token->type == JSMN_ARRAY) { + j = 0; + for (i = 0; i < token->size; i++) { + j += jsmn_get_total_size(token + 1 + j); + } + return j + 1; + } + return 0; +} + +/** + * Get token with a given key. + * The object (JSMN_OBJECT) to look within is set by input parameter 'token'. + */ +JSMN_API jsmntok_t *jsmn_get_token_by_key(const char *js, jsmntok_t *token, + const char *key) { + unsigned int i; + int res = -1; + size_t key_len = 0; + const char *tmp_key = key; + size_t total_size; + + if (token->type != JSMN_OBJECT) + return NULL; + + total_size = jsmn_get_total_size(token); + + while (*tmp_key != '\0' && tmp_key++) { + ; + } + key_len = tmp_key - key; + + for (i = 1; i < total_size; i++) { + int j; + int len = token[i].end - token[i].start; + int match = 1; + + if (len == key_len) { + for (j = 0; j < len; ++j) { + if (js[token[i].start + j] != key[j]) { + match = 0; + break; + } + } + } else { + match = 0; + } + + if (match == 1) { + res = i; + break; + } + + i += jsmn_get_total_size(&token[i + 1]); + } + + if (res == -1) + return NULL; + return &token[res]; +} + +/** + * Get token with a given index inside the array defined by 'token' parameter. + */ +JSMN_API jsmntok_t *jsmn_get_token_by_index(jsmntok_t *token, + unsigned int index) { + int i; + int res = -1; + int total_size; + + if (token->type != JSMN_ARRAY) + return NULL; + + total_size = jsmn_get_total_size(token); + for (i = 1; i < total_size; i++) { + if (index == 0) { + return &token[i]; + } + + i += jsmn_get_total_size(&token[i]) - 1; + --index; + } + + return NULL; +} + #endif /* JSMN_HEADER */ #ifdef __cplusplus From 16222377b3473edaae14c628d3f0dacb8f0d52f8 Mon Sep 17 00:00:00 2001 From: Maxim Menshikov Date: Wed, 1 May 2019 09:37:46 +0300 Subject: [PATCH 2/4] Test size calculation, key/index-based retrievals --- test/tests.c | 204 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 204 insertions(+) diff --git a/test/tests.c b/test/tests.c index d8a4d922..015f2202 100644 --- a/test/tests.c +++ b/test/tests.c @@ -336,6 +336,207 @@ int test_object_key(void) { return 0; } +int test_get_total_size(void) { + jsmn_parser p; + const char *js; + jsmntok_t tok[128]; + int ret; + + js = "{}"; + jsmn_init(&p); + ret = jsmn_parse(&p, js, strlen(js), tok, sizeof(tok) / sizeof(*tok)); + check(ret == 1); + check(jsmn_get_total_size(tok) == ret); + + js = "{ \"el_1\": 5 }"; + jsmn_init(&p); + ret = jsmn_parse(&p, js, strlen(js), tok, sizeof(tok) / sizeof(*tok)); + check(ret == 3); + check(jsmn_get_total_size(tok) == ret); + check(jsmn_get_total_size(tok + 1) == 1); + check(jsmn_get_total_size(tok + 2) == 1); + + /* Check size of two-element object */ + js = "{ \"el_1\": 2, \"el_3\": 4 }"; + jsmn_init(&p); + ret = jsmn_parse(&p, js, strlen(js), tok, sizeof(tok) / sizeof(*tok)); + check(ret == 5); + check(jsmn_get_total_size(tok) == ret); + + /* Check array handling */ + js = "{ \"el_1\": [ 3, 4 ], \"el_5\": 6, \"el_7\": 8 }"; + jsmn_init(&p); + ret = jsmn_parse(&p, js, strlen(js), tok, sizeof(tok) / sizeof(*tok)); + check(ret == 9); + check(jsmn_get_total_size(tok) == ret); + check(jsmn_get_total_size(tok + 2) == 3); + check(jsmn_get_total_size(tok + 3) == 1); + + /* Check string handling */ + js = "{ \"el_1\": \"el_2\", \"el_3\": \"el_4\", \"el_5\": \"el_6\" }"; + jsmn_init(&p); + ret = jsmn_parse(&p, js, strlen(js), tok, sizeof(tok) / sizeof(*tok)); + check(ret == 7); + check(jsmn_get_total_size(tok) == ret); + + /* Check nesting */ + js = "{ \"el_1\": { \"el_3\": \"el_4\", \"el_5\": [ 7, 8 ], \"el_9\": 10 } }"; + jsmn_init(&p); + ret = jsmn_parse(&p, js, strlen(js), tok, sizeof(tok) / sizeof(*tok)); + check(ret == 11); + check(jsmn_get_total_size(tok) == ret); + check(jsmn_get_total_size(tok + 2) == 9); + check(jsmn_get_total_size(tok + 6) == 3); + check(jsmn_get_total_size(tok + 9) == 1); + check(jsmn_get_total_size(tok + 10) == 1); + + /* Check arrays */ + js = "[ 6 7 ]"; + jsmn_init(&p); + ret = jsmn_parse(&p, js, strlen(js), tok, sizeof(tok) / sizeof(*tok)); + check(ret == 3); + + check(jsmn_get_total_size(tok) == ret); + check(jsmn_get_total_size(tok + 1) == 1); + check(jsmn_get_total_size(tok + 2) == 1); + return 0; +} + +int test_get_by_key(void) { + jsmn_parser p; + const char *js; + jsmntok_t tok[128]; + int ret; + + /* No element found in case there is only a top element */ + js = "{}"; + jsmn_init(&p); + ret = jsmn_parse(&p, js, strlen(js), tok, sizeof(tok) / sizeof(*tok)); + check(ret == 1); + check(jsmn_get_token_by_key(js, tok, "name") == NULL); + check(jsmn_get_token_by_key(js, tok, "") == NULL); + + js = "{ \"el_1\": 2 }"; + jsmn_init(&p); + ret = jsmn_parse(&p, js, strlen(js), tok, sizeof(tok) / sizeof(*tok)); + check(ret == 3); + + /* Find after top level should succeed */ + check(jsmn_get_token_by_key(js, tok + 1, "el_1") == NULL); + /* Find on top-level will find nothing because of nesting level */ + check(jsmn_get_token_by_key(js, tok, "el_1") == &tok[1]); + + /* Find when there are two elements */ + js = "{ \"el_1\": 2, \"el_3\": 4 }"; + jsmn_init(&p); + ret = jsmn_parse(&p, js, strlen(js), tok, sizeof(tok) / sizeof(*tok)); + check(ret == 5); + + check(jsmn_get_token_by_key(js, tok, "el_1") == &tok[1]); + check(jsmn_get_token_by_key(js, tok, "el_3") == &tok[3]); + + /* Check array handling */ + js = "{ \"el_1\": [ 3, 4 ], \"el_5\": 6, \"el_7\": 8 }"; + jsmn_init(&p); + ret = jsmn_parse(&p, js, strlen(js), tok, sizeof(tok) / sizeof(*tok)); + check(ret == 9); + + check(jsmn_get_token_by_key(js, tok, "el_1") == &tok[1]); + check(jsmn_get_token_by_key(js, tok, "el_5") == &tok[5]); + check(jsmn_get_token_by_key(js, tok, "el_7") == &tok[7]); + + /* Check string handling */ + js = "{ \"el_1\": \"el_2\", \"el_3\": \"el_4\", \"el_5\": \"el_6\" }"; + jsmn_init(&p); + ret = jsmn_parse(&p, js, strlen(js), tok, sizeof(tok) / sizeof(*tok)); + check(ret == 7); + + check(jsmn_get_token_by_key(js, tok, "el_1") == &tok[1]); + check(jsmn_get_token_by_key(js, tok, "el_2") == NULL); + check(jsmn_get_token_by_key(js, tok, "el_3") == &tok[3]); + check(jsmn_get_token_by_key(js, tok, "el_4") == NULL); + check(jsmn_get_token_by_key(js, tok, "el_5") == &tok[5]); + check(jsmn_get_token_by_key(js, tok, "el_6") == NULL); + + /* Check nesting */ + js = "{ \"el_1\": { \"el_3\": \"el_4\", \"el_5\": [ 7, 8 ], \"el_9\": 10 } }"; + jsmn_init(&p); + ret = jsmn_parse(&p, js, strlen(js), tok, sizeof(tok) / sizeof(*tok)); + check(ret == 11); + + check(jsmn_get_token_by_key(js, tok, "el_1") == &tok[1]); + check(jsmn_get_token_by_key(js, tok, "el_9") == NULL); + check(jsmn_get_token_by_key(js, tok + 2, "el_9") == &tok[9]); + + /* Check arrays */ + js = "[ 1 2 ]"; + jsmn_init(&p); + ret = jsmn_parse(&p, js, strlen(js), tok, sizeof(tok) / sizeof(*tok)); + check(ret == 3); + + check(jsmn_get_token_by_key(js, tok, "el_1") == NULL); + return 0; +} + + +int test_get_by_index(void) { + jsmn_parser p; + const char *js; + jsmntok_t tok[128]; + int ret; + + /* Object handling */ + js = "{}"; + jsmn_init(&p); + ret = jsmn_parse(&p, js, strlen(js), tok, sizeof(tok) / sizeof(*tok)); + check(ret == 1); + check(jsmn_get_token_by_index(tok, 0) == NULL); + check(jsmn_get_token_by_index(tok, 1) == NULL); + check(jsmn_get_token_by_index(tok, 2) == NULL); + + js = "{ \"el_1\": 2 }"; + jsmn_init(&p); + ret = jsmn_parse(&p, js, strlen(js), tok, sizeof(tok) / sizeof(*tok)); + check(ret == 3); + check(jsmn_get_token_by_index(tok, 0) == NULL); + check(jsmn_get_token_by_index(tok, 1) == NULL); + check(jsmn_get_token_by_index(tok, 2) == NULL); + + /* Trivial arrays */ + js = "[ \"el_1\", 2 ]"; + jsmn_init(&p); + ret = jsmn_parse(&p, js, strlen(js), tok, sizeof(tok) / sizeof(*tok)); + check(ret == 3); + check(jsmn_get_token_by_index(tok, 0) == &tok[1]); + check(jsmn_get_token_by_index(tok, 1) == &tok[2]); + check(jsmn_get_token_by_index(tok, 2) == NULL); + + js = "[ \"el_1\", 2, \"el_3\", 4 ]"; + jsmn_init(&p); + ret = jsmn_parse(&p, js, strlen(js), tok, sizeof(tok) / sizeof(*tok)); + check(ret == 5); + check(jsmn_get_token_by_index(tok, 0) == &tok[1]); + check(jsmn_get_token_by_index(tok, 1) == &tok[2]); + check(jsmn_get_token_by_index(tok, 2) == &tok[3]); + check(jsmn_get_token_by_index(tok, 3) == &tok[4]); + + /* Simple nested arrays */ + js = "[ [ 2, 3 ], 4, [ 6, 7, 8, 9] ]"; + jsmn_init(&p); + ret = jsmn_parse(&p, js, strlen(js), tok, sizeof(tok) / sizeof(*tok)); + check(ret == 10); + check(jsmn_get_token_by_index(tok, 0) == &tok[1]); + check(jsmn_get_token_by_index(tok, 1) == &tok[4]); + check(jsmn_get_token_by_index(tok, 2) == &tok[5]); + check(jsmn_get_token_by_index(tok + 1, 0) == &tok[2]); + check(jsmn_get_token_by_index(tok + 1, 1) == &tok[3]); + check(jsmn_get_token_by_index(tok + 1, 2) == NULL); + + return 0; +} + + + int main(void) { test(test_empty, "test for a empty JSON objects/arrays"); test(test_object, "test for a JSON objects"); @@ -354,6 +555,9 @@ int main(void) { test(test_nonstrict, "test for non-strict mode"); test(test_unmatched_brackets, "test for unmatched brackets"); test(test_object_key, "test for key type"); + test(test_get_total_size, "test size calculation"); + test(test_get_by_key, "test key-based retrieval"); + test(test_get_by_index, "test index-based retrieval"); printf("\nPASSED: %d\nFAILED: %d\n", test_passed, test_failed); return (test_failed > 0); } From abd83454cf35b3393455917d8c4faad998c9cad4 Mon Sep 17 00:00:00 2001 From: Maxim Menshikov Date: Wed, 1 May 2019 09:44:54 +0300 Subject: [PATCH 3/4] Add definitions of get_total_size and key/index-based retrievals to a header part --- jsmn.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/jsmn.h b/jsmn.h index 5f08891a..0a9a8cbc 100644 --- a/jsmn.h +++ b/jsmn.h @@ -99,6 +99,23 @@ JSMN_API void jsmn_init(jsmn_parser *parser); JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, jsmntok_t *tokens, const unsigned int num_tokens); +/** + * Get the size (in tokens) consumed by token and its children. + */ +JSMN_API size_t jsmn_get_total_size(jsmntok_t *token); +/** + * Get token with a given key. + * The object (JSMN_OBJECT) to look within is set by input parameter 'token'. + */ +JSMN_API jsmntok_t *jsmn_get_token_by_key(const char *js, jsmntok_t *token, + const char *key); + +/** + * Get token with a given index inside the array defined by 'token' parameter. + */ +JSMN_API jsmntok_t *jsmn_get_token_by_index(jsmntok_t *token, + unsigned int index); + #ifndef JSMN_HEADER /** * Allocates a fresh unused token from the token pool. From 5b29285bc378af95d32ac51c4b80a229d34eff61 Mon Sep 17 00:00:00 2001 From: Maxim Menshikov Date: Fri, 16 Aug 2019 14:54:47 +0300 Subject: [PATCH 4/4] Fix clang-tidy mistakes, apply formatting --- jsmn.h | 44 +++++++++++++++++++++++++------------------- test/tests.c | 15 ++++++--------- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/jsmn.h b/jsmn.h index 0a9a8cbc..b8cb2186 100644 --- a/jsmn.h +++ b/jsmn.h @@ -113,7 +113,7 @@ JSMN_API jsmntok_t *jsmn_get_token_by_key(const char *js, jsmntok_t *token, /** * Get token with a given index inside the array defined by 'token' parameter. */ -JSMN_API jsmntok_t *jsmn_get_token_by_index(jsmntok_t *token, +JSMN_API jsmntok_t *jsmn_get_token_by_index(jsmntok_t *token, unsigned int index); #ifndef JSMN_HEADER @@ -481,12 +481,13 @@ JSMN_API void jsmn_init(jsmn_parser *parser) { */ JSMN_API size_t jsmn_get_total_size(jsmntok_t *token) { unsigned int i, j; - jsmntok_t *key; + jsmntok_t *key; + int result = 0; if (token->type == JSMN_PRIMITIVE) { - return 1; + result = 1; } else if (token->type == JSMN_STRING) { - return 1; + result = 1; } else if (token->type == JSMN_OBJECT) { j = 0; for (i = 0; i < token->size; i++) { @@ -496,15 +497,16 @@ JSMN_API size_t jsmn_get_total_size(jsmntok_t *token) { j += jsmn_get_total_size(token + 1 + j); } } - return j + 1; + result = j + 1; } else if (token->type == JSMN_ARRAY) { j = 0; for (i = 0; i < token->size; i++) { j += jsmn_get_total_size(token + 1 + j); } - return j + 1; + result = j + 1; } - return 0; + + return result; } /** @@ -513,14 +515,15 @@ JSMN_API size_t jsmn_get_total_size(jsmntok_t *token) { */ JSMN_API jsmntok_t *jsmn_get_token_by_key(const char *js, jsmntok_t *token, const char *key) { - unsigned int i; - int res = -1; - size_t key_len = 0; - const char *tmp_key = key; - size_t total_size; + unsigned int i; + int res = -1; + size_t key_len = 0; + const char *tmp_key = key; + size_t total_size; - if (token->type != JSMN_OBJECT) + if (token->type != JSMN_OBJECT) { return NULL; + } total_size = jsmn_get_total_size(token); @@ -553,22 +556,25 @@ JSMN_API jsmntok_t *jsmn_get_token_by_key(const char *js, jsmntok_t *token, i += jsmn_get_total_size(&token[i + 1]); } - if (res == -1) + if (res == -1) { return NULL; + } + return &token[res]; } /** * Get token with a given index inside the array defined by 'token' parameter. */ -JSMN_API jsmntok_t *jsmn_get_token_by_index(jsmntok_t *token, +JSMN_API jsmntok_t *jsmn_get_token_by_index(jsmntok_t *token, unsigned int index) { - int i; - int res = -1; - int total_size; + int i; + int res = -1; + int total_size; - if (token->type != JSMN_ARRAY) + if (token->type != JSMN_ARRAY) { return NULL; + } total_size = jsmn_get_total_size(token); for (i = 1; i < total_size; i++) { diff --git a/test/tests.c b/test/tests.c index 015f2202..a8eb00a7 100644 --- a/test/tests.c +++ b/test/tests.c @@ -339,8 +339,8 @@ int test_object_key(void) { int test_get_total_size(void) { jsmn_parser p; const char *js; - jsmntok_t tok[128]; - int ret; + jsmntok_t tok[128]; + int ret; js = "{}"; jsmn_init(&p); @@ -405,8 +405,8 @@ int test_get_total_size(void) { int test_get_by_key(void) { jsmn_parser p; const char *js; - jsmntok_t tok[128]; - int ret; + jsmntok_t tok[128]; + int ret; /* No element found in case there is only a top element */ js = "{}"; @@ -478,12 +478,11 @@ int test_get_by_key(void) { return 0; } - int test_get_by_index(void) { jsmn_parser p; const char *js; - jsmntok_t tok[128]; - int ret; + jsmntok_t tok[128]; + int ret; /* Object handling */ js = "{}"; @@ -535,8 +534,6 @@ int test_get_by_index(void) { return 0; } - - int main(void) { test(test_empty, "test for a empty JSON objects/arrays"); test(test_object, "test for a JSON objects");