From f22c2d30b7c73ebf1a7815b4a3eb5df18c251ed1 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Mon, 15 Nov 2010 13:11:08 +0200 Subject: Initial commit. Demo program is included in the jsmn.c code. Ugly names and no comments. Please, don't read this changeset --- Makefile | 13 +++++ jsmn.c | 198 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ jsmn.h | 17 ++++++ 3 files changed, 228 insertions(+) create mode 100644 Makefile create mode 100644 jsmn.c create mode 100644 jsmn.h diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..c6816e976 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +CFLAGS=-Wall -W -std=c89 + +all: jsmn_demo + +jsmn_demo: jsmn.o + gcc $(LDFLAGS) jsmn.o -o $@ + +jsmn.o: jsmn.c jsmn.h + gcc $(CFLAGS) -c jsmn.c -o $@ + +clean: + rm -f jsmn.o + rm -f jsmn_demo diff --git a/jsmn.c b/jsmn.c new file mode 100644 index 000000000..334249476 --- /dev/null +++ b/jsmn.c @@ -0,0 +1,198 @@ +#include +#include + +#include "jsmn.h" + +enum { + JSON_SKIP = 0, + JSON_OPEN = 1, + JSON_CLOSE = 2, + JSON_BARE = 3, + JSON_UNBARE = 4, + JSON_QUOTE = 5, + JSON_UNQUOTE = 6, + JSON_ERROR = 7 +}; + +#define JSON_SYM_SKIP(sym) \ + [sym] = JSON_SKIP + +#define JSON_SYM_ERROR(sym) \ + [sym] = JSON_ERROR + +#define JSON_SYM_OPEN(sym) \ + [sym] = JSON_OPEN + +#define JSON_SYM_CLOSE(sym) \ + [sym] = JSON_CLOSE + +#define JSON_SYM_BARE(sym) \ + [sym] = JSON_BARE + +#define JSON_SYM_UNBARE(sym) \ + [sym] = JSON_UNBARE + +#define JSON_SYM_QUOTE(sym) \ + [sym] = JSON_QUOTE + +#define JSON_SYM_UNQUOTE(sym) \ + [sym] = JSON_UNQUOTE + +int jsmn_parse(const unsigned char *js, jsontok_t *tokens, size_t num_tokens, int **errpos) { + + int jsmn_token_start(jsontype_t type, int pos) { + unsigned int i; + for (i = 0; i= 0; i--) { + if (tokens[i].type == type && tokens[i].start != -1 && tokens[i].end == -1) { + tokens[i].end = pos; + return 0; + } + } + return -1; + } + + const unsigned char *p; + jsontok_t *cur_token; + + int obj_common[] = { + JSON_SYM_ERROR(0 ... 255), + JSON_SYM_SKIP('\t'), JSON_SYM_SKIP('\r'),JSON_SYM_SKIP('\n'), + JSON_SYM_SKIP(':'), JSON_SYM_SKIP(','), JSON_SYM_SKIP(' '), + JSON_SYM_QUOTE('\"'), + JSON_SYM_OPEN('['), JSON_SYM_CLOSE(']'), + JSON_SYM_OPEN('{'), JSON_SYM_CLOSE('}'), + JSON_SYM_BARE('-'), JSON_SYM_BARE('0'...'9'), + JSON_SYM_BARE('t'), JSON_SYM_BARE('f'), JSON_SYM_BARE('n') /* true false null */ + }; + + int obj_bare[] = { + JSON_SYM_ERROR(0 ... 31), + JSON_SYM_ERROR(127 ... 255), + JSON_SYM_SKIP(32 ... 126), + JSON_SYM_UNBARE('\t'), JSON_SYM_UNBARE(' '), + JSON_SYM_UNBARE('\r'), JSON_SYM_UNBARE('\n'), + JSON_SYM_UNBARE(','), JSON_SYM_UNBARE(']'), + JSON_SYM_UNBARE('}') + }; + + int obj_string[] = { + JSON_SYM_ERROR(0 ... 31), JSON_SYM_ERROR(127), + JSON_SYM_SKIP(32 ... 126), + JSON_SYM_UNQUOTE('\"'), + JSON_SYM_ERROR(248 ... 255), + }; + + int *obj_state = obj_common; + + cur_token = tokens; + + int i; + for (i = 0; istart, obj->end); + len = obj->end - obj->start; + + char *type; + switch (obj->type) { + case JSON_OTHER: + type = "other"; + break; + case JSON_STRING: + type = "string"; + break; + case JSON_ARRAY: + type = "array"; + break; + case JSON_OBJECT: + type = "object"; + break; + } + + printf(" %s ", type); + + if (len > 0) { + char *s = strndup(&js[obj->start], len); + printf("%s", s); + free(s); + } + printf("\n"); +} + +int main(int argc, char *argv[]) { + int i; +#define NUM_TOKENS 20 + jsontok_t tokens[NUM_TOKENS]; + + const char *js = + "{" + "\"foo\": \"bar\"," + "\"bar\": [1,2, 3]," + "\"obj\": { \"true\": false}" + "}"; + + jsmn_parse(js, tokens, NUM_TOKENS, NULL); + + for (i = 0; i Date: Mon, 15 Nov 2010 13:27:14 +0200 Subject: README and LICENSE added. MIT license choosen. --- LICENSE | 20 ++++++++++++++++++++ README | 25 +++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 LICENSE create mode 100644 README diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..c84fb2e97 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2010 Serge A. Zaitsev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/README b/README new file mode 100644 index 000000000..1c4bd74ea --- /dev/null +++ b/README @@ -0,0 +1,25 @@ +JSMN - Minimalistic JSON parser library +======================================= + +jsmn (pronounced right as `jasmine`) is a simple and clean library for parsing +JSON (JavaScript Object Notation) data format. + +You can find more information on JSON at http://www.json.org/ + +Philosophy +========== + +jsmn will never be large and complex. This means it will never have extra +features or any bloated dependencies, besides libc. +It will always be suitable for embedded systems and other resource-limited +systems. It will always have clear API. + +Features +======== + +o Just 200 lines of code +o Full C89 compatibility +o No memory allocation inside the library code +o A single pass is required to parse JSON data +o MIT license (you can include this code into your proprietary software) + -- cgit v1.2.3 From 0568be6e0b1304226ddf8b17f3df4a5dfa2babdd Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Mon, 15 Nov 2010 15:00:42 +0200 Subject: Demo separated from the jsmn code. Makefile changed. Some comments added --- Makefile | 16 ++++++++++------ demo.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ jsmn.c | 60 ++-------------------------------------------------------- jsmn.h | 23 +++++++++++++++++++++- 4 files changed, 100 insertions(+), 65 deletions(-) create mode 100644 demo.c diff --git a/Makefile b/Makefile index c6816e976..cda64f530 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,17 @@ -CFLAGS=-Wall -W -std=c89 +CFLAGS=-Wall -std=c89 + +OBJS=jsmn.o demo.o all: jsmn_demo -jsmn_demo: jsmn.o - gcc $(LDFLAGS) jsmn.o -o $@ +jsmn_demo: $(OBJS) + $(CC) $(LDFLAGS) $(OBJS) -o $@ -jsmn.o: jsmn.c jsmn.h - gcc $(CFLAGS) -c jsmn.c -o $@ +%.o: %.c jsmn.h + $(CC) -c $(CFLAGS) $< -o $@ clean: - rm -f jsmn.o + rm -f $(OBJS) rm -f jsmn_demo + +.PHONY: clean all diff --git a/demo.c b/demo.c new file mode 100644 index 000000000..e644dcb9f --- /dev/null +++ b/demo.c @@ -0,0 +1,66 @@ +/* This demo is not needed to be C89-compatible, so for now GCC extensions are + * used */ +#define _GNU_SOURCE + +#include +#include +#include + +#include "jsmn.h" + +#define NUM_TOKENS 20 + +static void json_dump_obj(jsontok_t *obj, const unsigned char *js) { + size_t len; + + printf("[%d,%d]", obj->start, obj->end); + len = (size_t) (obj->end - obj->start); + + char *type; + switch (obj->type) { + case JSON_OTHER: + type = "other"; + break; + case JSON_NUMBER: + type = "number"; + break; + case JSON_STRING: + type = "string"; + break; + case JSON_ARRAY: + type = "array"; + break; + case JSON_OBJECT: + type = "object"; + break; + } + + printf(" %s ", type); + + if (len > 0) { + char *s = strndup((const char *) &js[obj->start], len); + printf("%s", s); + free(s); + } + printf("\n"); +} + +int main(void) { + int i; + jsontok_t tokens[NUM_TOKENS]; + + const unsigned char *js = (unsigned char *) + "{" + "\"foo\": \"bar\"," + "\"bar\": [1,2, 3]," + "\"obj\": { \"true\": false}" + "}"; + + jsmn_parse(js, tokens, NUM_TOKENS, NULL); + + for (i = 0; i -#include #include "jsmn.h" @@ -53,7 +52,7 @@ int jsmn_parse(const unsigned char *js, jsontok_t *tokens, size_t num_tokens, in } int jsmn_token_end(jsontype_t type, int pos) { - unsigned int i; + int i; for (i = num_tokens - 1; i>= 0; i--) { if (tokens[i].type == type && tokens[i].start != -1 && tokens[i].end == -1) { tokens[i].end = pos; @@ -64,7 +63,6 @@ int jsmn_parse(const unsigned char *js, jsontok_t *tokens, size_t num_tokens, in } const unsigned char *p; - jsontok_t *cur_token; int obj_common[] = { JSON_SYM_ERROR(0 ... 255), @@ -96,9 +94,7 @@ int jsmn_parse(const unsigned char *js, jsontok_t *tokens, size_t num_tokens, in int *obj_state = obj_common; - cur_token = tokens; - - int i; + unsigned int i; for (i = 0; istart, obj->end); - len = obj->end - obj->start; - - char *type; - switch (obj->type) { - case JSON_OTHER: - type = "other"; - break; - case JSON_STRING: - type = "string"; - break; - case JSON_ARRAY: - type = "array"; - break; - case JSON_OBJECT: - type = "object"; - break; - } - - printf(" %s ", type); - - if (len > 0) { - char *s = strndup(&js[obj->start], len); - printf("%s", s); - free(s); - } - printf("\n"); -} - -int main(int argc, char *argv[]) { - int i; -#define NUM_TOKENS 20 - jsontok_t tokens[NUM_TOKENS]; - - const char *js = - "{" - "\"foo\": \"bar\"," - "\"bar\": [1,2, 3]," - "\"obj\": { \"true\": false}" - "}"; - - jsmn_parse(js, tokens, NUM_TOKENS, NULL); - - for (i = 0; i Date: Mon, 15 Nov 2010 15:02:35 +0200 Subject: Fix: error position is an offset, not a pointer --- jsmn.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsmn.c b/jsmn.c index 50123373e..f11cd04d9 100644 --- a/jsmn.c +++ b/jsmn.c @@ -104,7 +104,7 @@ int jsmn_parse(const unsigned char *js, jsontok_t *tokens, size_t num_tokens, in switch (obj_state[*p]) { case JSON_ERROR: if (errpos != NULL) { - *errpos = p; + *errpos = p - js; } return -1; -- cgit v1.2.3 From aa97d8b59960ff007632ac5a6a6993a46935092e Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Mon, 15 Nov 2010 15:46:31 +0200 Subject: Demo: changed to read from '.js' file --- demo.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 23 deletions(-) diff --git a/demo.c b/demo.c index e644dcb9f..788017f10 100644 --- a/demo.c +++ b/demo.c @@ -10,57 +10,87 @@ #define NUM_TOKENS 20 -static void json_dump_obj(jsontok_t *obj, const unsigned char *js) { +static void json_dump_obj(jsontok_t *obj, const char *js) { size_t len; - printf("[%d,%d]", obj->start, obj->end); - len = (size_t) (obj->end - obj->start); + if (obj->end < 0 || obj->start < 0) { + return; + } + + len = obj->end - obj->start; + + printf("[%d,%d]\t", obj->start, obj->end); char *type; switch (obj->type) { case JSON_OTHER: - type = "other"; + type = "(?)"; break; case JSON_NUMBER: - type = "number"; + type = "(N)"; break; case JSON_STRING: - type = "string"; + type = "(S)"; break; case JSON_ARRAY: - type = "array"; + type = "(A)"; break; case JSON_OBJECT: - type = "object"; + type = "(O)"; break; } - printf(" %s ", type); + printf("%s ", type); - if (len > 0) { - char *s = strndup((const char *) &js[obj->start], len); - printf("%s", s); - free(s); - } - printf("\n"); + char *s = strndup((const char *) &js[obj->start], len); + printf("%s\n", s); + free(s); } -int main(void) { +int main(int argc, char *argv[]) { int i; jsontok_t tokens[NUM_TOKENS]; + FILE *f; + int filesize = 0; + char *js = NULL; + + if (argc != 2) { + fprintf(stderr, "Usage: ./demo \n"); + exit(EXIT_SUCCESS); + } - const unsigned char *js = (unsigned char *) - "{" - "\"foo\": \"bar\"," - "\"bar\": [1,2, 3]," - "\"obj\": { \"true\": false}" - "}"; + f = fopen(argv[1], "r"); + if (f == NULL) { + fprintf(stderr, "Failed to open file `%s`\n", argv[1]); + exit(EXIT_FAILURE); + } + + while (1) { + int r; + char buf[BUFSIZ]; + r = fread(buf, 1, BUFSIZ, f); + if (r <= 0) { + break; + } + js = (char *) realloc(js, filesize + r); + if (js == NULL) { + fprintf(stderr, "Cannot allocate anough memory\n"); + fclose(f); + exit(EXIT_FAILURE); + } + memcpy(js + filesize, buf, r); + filesize += r; + } + + fclose(f); - jsmn_parse(js, tokens, NUM_TOKENS, NULL); + jsmn_parse((unsigned char *) js, tokens, NUM_TOKENS, NULL); for (i = 0; i Date: Mon, 15 Nov 2010 17:39:25 +0200 Subject: Design: unrolled reference tables. Nested functions moved outside. Got smaller code. --- demo.c | 9 ++- jsmn.c | 224 ++++++++++++++++++++++++++++++++++++----------------------------- 2 files changed, 130 insertions(+), 103 deletions(-) diff --git a/demo.c b/demo.c index 788017f10..240d8a5a5 100644 --- a/demo.c +++ b/demo.c @@ -49,6 +49,8 @@ static void json_dump_obj(jsontok_t *obj, const char *js) { int main(int argc, char *argv[]) { int i; + int r; + int errpos; jsontok_t tokens[NUM_TOKENS]; FILE *f; int filesize = 0; @@ -66,7 +68,6 @@ int main(int argc, char *argv[]) { } while (1) { - int r; char buf[BUFSIZ]; r = fread(buf, 1, BUFSIZ, f); if (r <= 0) { @@ -84,7 +85,11 @@ int main(int argc, char *argv[]) { fclose(f); - jsmn_parse((unsigned char *) js, tokens, NUM_TOKENS, NULL); + r = jsmn_parse((unsigned char *) js, tokens, NUM_TOKENS, &errpos); + if (r < 0) { + printf("error at pos %d: %s\n", errpos, &js[errpos]); + exit(EXIT_FAILURE); + } for (i = 0; istart] != '\"') { + return -1; + } -#define JSON_SYM_CLOSE(sym) \ - [sym] = JSON_CLOSE + /* Skip starting quote */ + token->start++; -#define JSON_SYM_BARE(sym) \ - [sym] = JSON_BARE + for (p = &js[token->start]; *p != '\0'; p++) { + /* Quote: end of string */ + if (*p == '\"') { + token->end = p - js; + return 0; + } -#define JSON_SYM_UNBARE(sym) \ - [sym] = JSON_UNBARE + /* Backslash: Quoted symbol expected */ + if (*p == '\\') { + p++; + switch (*p) { + /* Allowed escaped symbols */ + case '\"': case '/' : case '\\' : case 'b' : + case 'f' : case 'r' : case 'n' : case 't' : + break; + /* Allows escaped symbol \uXXXX */ + case 'u': + /* TODO */ + break; + /* Unexpected symbol */ + default: + return -1; + } + } + } + return -1; +} -#define JSON_SYM_QUOTE(sym) \ - [sym] = JSON_QUOTE +static int jsmn_parse_primitive(const unsigned char *js, jsontok_t *token) { + const unsigned char *p; -#define JSON_SYM_UNQUOTE(sym) \ - [sym] = JSON_UNQUOTE + for (p = &js[token->start]; *p != '\0'; p++) { + switch (*p) { + case '\t' : case '\r' : case '\n' : case ' ' : + case ',' : case ']' : case '}' : + token->end = p - js; + return 0; + } + if (*p < 32 || *p >= 127) { + return -1; + } + } + return -1; +} -int jsmn_parse(const unsigned char *js, jsontok_t *tokens, size_t num_tokens, int **errpos) { +static void jsmn_error(struct jsmn_params *params, int pos) { + if (params->errpos != NULL) { + *params->errpos = pos; + } +} - int jsmn_token_start(jsontype_t type, int pos) { - unsigned int i; - for (i = 0; itokens; + for (i = 0; inum_tokens; i++) { + if (tokens[i].start == -1 && tokens[i].end == -1) { + tokens[i].start = pos; + tokens[i].type = type; + return &tokens[i]; } - return -1; } + return NULL; +} - int jsmn_token_end(jsontype_t type, int pos) { - int i; - for (i = num_tokens - 1; i>= 0; i--) { - if (tokens[i].type == type && tokens[i].start != -1 && tokens[i].end == -1) { - tokens[i].end = pos; - return 0; - } +static jsontok_t *jsmn_token_end(struct jsmn_params *params, jsontype_t type, int pos) { + int i; + jsontok_t *tokens = params->tokens; + for (i = params->num_tokens - 1; i>= 0; i--) { + if (tokens[i].type == type && tokens[i].start != -1 && tokens[i].end == -1) { + tokens[i].end = pos; + return &tokens[i]; } - return -1; } - + return NULL; +} + +int jsmn_parse(const unsigned char *js, jsontok_t *tokens, size_t num_tokens, int **errpos) { + + struct jsmn_params params; + + int r; const unsigned char *p; + jsontok_t *cur_token; - int obj_common[] = { - JSON_SYM_ERROR(0 ... 255), - JSON_SYM_SKIP('\t'), JSON_SYM_SKIP('\r'),JSON_SYM_SKIP('\n'), - JSON_SYM_SKIP(':'), JSON_SYM_SKIP(','), JSON_SYM_SKIP(' '), - JSON_SYM_QUOTE('\"'), - JSON_SYM_OPEN('['), JSON_SYM_CLOSE(']'), - JSON_SYM_OPEN('{'), JSON_SYM_CLOSE('}'), - JSON_SYM_BARE('-'), JSON_SYM_BARE('0'...'9'), - JSON_SYM_BARE('t'), JSON_SYM_BARE('f'), JSON_SYM_BARE('n') /* true false null */ - }; - - int obj_bare[] = { - JSON_SYM_ERROR(0 ... 31), - JSON_SYM_ERROR(127 ... 255), - JSON_SYM_SKIP(32 ... 126), - JSON_SYM_UNBARE('\t'), JSON_SYM_UNBARE(' '), - JSON_SYM_UNBARE('\r'), JSON_SYM_UNBARE('\n'), - JSON_SYM_UNBARE(','), JSON_SYM_UNBARE(']'), - JSON_SYM_UNBARE('}') - }; - - int obj_string[] = { - JSON_SYM_ERROR(0 ... 31), JSON_SYM_ERROR(127), - JSON_SYM_SKIP(32 ... 126), - JSON_SYM_UNQUOTE('\"'), - JSON_SYM_ERROR(248 ... 255), - }; - - int *obj_state = obj_common; + params.num_tokens = num_tokens; + params.tokens = tokens; + params.errpos = errpos; unsigned int i; for (i = 0; istart = p - js; break; - case JSON_CLOSE: - jsmn_token_end(JSON_OBJECT, p - js + 1); + case '}' : case ']': + cur_token = jsmn_token_end(¶ms, JSON_OBJECT, p - js + 1); + cur_token->end = p - js + 1; break; - case JSON_BARE: - jsmn_token_start(JSON_OTHER, p - js); - obj_state = obj_bare; - break; - case JSON_UNBARE: - jsmn_token_end(JSON_OTHER, p - js); - obj_state = obj_common; - continue; - - case JSON_QUOTE: - jsmn_token_start(JSON_STRING, p - js + 1); - obj_state = obj_string; + case '-': case '0': case '1' : case '2': case '3' : case '4': + case '5': case '6': case '7' : case '8': case '9': + case 't': case 'f': case 'n' : + cur_token = jsmn_token_start(¶ms, JSON_OTHER, p - js); + r = jsmn_parse_primitive(js, cur_token); + if (r < 0) { + jsmn_error(¶ms, p - js); + return -1; + } + p = &js[cur_token->end]; break; - case JSON_UNQUOTE: - jsmn_token_end(JSON_STRING, p - js); - obj_state = obj_common; + + case '\"': + cur_token = jsmn_token_start(¶ms, JSON_STRING, p - js); + r = jsmn_parse_string(js, cur_token); + if (r < 0) { + jsmn_error(¶ms, p - js); + return -1; + } + p = &js[cur_token->end]; break; - case JSON_SKIP: + + case '\t' : case '\r' : case '\n' : case ':' : case ',': case ' ': break; + + default: + jsmn_error(¶ms, p - js); + return -1; } p++; } + jsmn_error(¶ms, 0); return 0; } -- cgit v1.2.3 From b0e73ec44dc2693b6be327c01b905193f153df4c Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Mon, 15 Nov 2010 17:58:50 +0200 Subject: Design: objects and arrays are different types now --- jsmn.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/jsmn.c b/jsmn.c index 4e2e405ba..bdaa49c5e 100644 --- a/jsmn.c +++ b/jsmn.c @@ -103,14 +103,15 @@ int jsmn_parse(const unsigned char *js, jsontok_t *tokens, size_t num_tokens, in struct jsmn_params params; int r; + unsigned int i; const unsigned char *p; + jsontype_t type; jsontok_t *cur_token; params.num_tokens = num_tokens; params.tokens = tokens; params.errpos = errpos; - unsigned int i; for (i = 0; istart = p - js; break; case '}' : case ']': - cur_token = jsmn_token_end(¶ms, JSON_OBJECT, p - js + 1); + type = (*p == '}' ? JSON_OBJECT : JSON_ARRAY); + cur_token = jsmn_token_end(¶ms, type, p - js + 1); cur_token->end = p - js + 1; break; -- cgit v1.2.3 From b99663079c1542e514f0ca8056cd87cb3683bd7e Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Mon, 15 Nov 2010 17:59:51 +0200 Subject: Fix: errpos has got a correct type now --- jsmn.c | 4 ++-- jsmn.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/jsmn.c b/jsmn.c index bdaa49c5e..7057bc811 100644 --- a/jsmn.c +++ b/jsmn.c @@ -5,7 +5,7 @@ struct jsmn_params { jsontok_t *tokens; size_t num_tokens; - int **errpos; + int *errpos; }; /** @@ -98,7 +98,7 @@ static jsontok_t *jsmn_token_end(struct jsmn_params *params, jsontype_t type, in return NULL; } -int jsmn_parse(const unsigned char *js, jsontok_t *tokens, size_t num_tokens, int **errpos) { +int jsmn_parse(const unsigned char *js, jsontok_t *tokens, size_t num_tokens, int *errpos) { struct jsmn_params params; diff --git a/jsmn.h b/jsmn.h index ed7c1b159..12dd9fd0b 100644 --- a/jsmn.h +++ b/jsmn.h @@ -33,6 +33,6 @@ typedef struct { * Run JSON parser. It parses a JSON data string into and array of tokens, each describing * a single JSON object. */ -int jsmn_parse(const unsigned char *js, jsontok_t *tokens, size_t num_tokens, int **errpos); +int jsmn_parse(const unsigned char *js, jsontok_t *tokens, size_t num_tokens, int *errpos); #endif /* __JSMN_H_ */ -- cgit v1.2.3 From 30370e37f879a0871ccbc078f8d3c86938b3a063 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Tue, 16 Nov 2010 11:45:10 +0200 Subject: Design: Added asserts in parser. Increased number of tokens in demo. --- demo.c | 8 ++++---- jsmn.c | 22 +++++++++++++--------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/demo.c b/demo.c index 240d8a5a5..a9dfa41f5 100644 --- a/demo.c +++ b/demo.c @@ -8,9 +8,9 @@ #include "jsmn.h" -#define NUM_TOKENS 20 +#define NUM_TOKENS 30 -static void json_dump_obj(jsontok_t *obj, const char *js) { +static void jsmn_dump_obj(jsontok_t *obj, const char *js) { size_t len; if (obj->end < 0 || obj->start < 0) { @@ -87,12 +87,12 @@ int main(int argc, char *argv[]) { r = jsmn_parse((unsigned char *) js, tokens, NUM_TOKENS, &errpos); if (r < 0) { - printf("error at pos %d: %s\n", errpos, &js[errpos]); + printf("error %d at pos %d: %s\n", r, errpos, &js[errpos]); exit(EXIT_FAILURE); } for (i = 0; istart = p - js; break; case '}' : case ']': type = (*p == '}' ? JSON_OBJECT : JSON_ARRAY); cur_token = jsmn_token_end(¶ms, type, p - js + 1); + jsmn_assert(cur_token != NULL, p - js, -1); cur_token->end = p - js + 1; break; @@ -134,21 +142,17 @@ int jsmn_parse(const unsigned char *js, jsontok_t *tokens, size_t num_tokens, in case '5': case '6': case '7' : case '8': case '9': case 't': case 'f': case 'n' : cur_token = jsmn_token_start(¶ms, JSON_OTHER, p - js); + jsmn_assert(cur_token != NULL, p - js, -1); r = jsmn_parse_primitive(js, cur_token); - if (r < 0) { - jsmn_error(¶ms, p - js); - return -1; - } - p = &js[cur_token->end]; + jsmn_assert(r == 0, p - js, -2); + p = &js[cur_token->end] - 1; break; case '\"': cur_token = jsmn_token_start(¶ms, JSON_STRING, p - js); + jsmn_assert(cur_token != NULL, p - js, -1); r = jsmn_parse_string(js, cur_token); - if (r < 0) { - jsmn_error(¶ms, p - js); - return -1; - } + jsmn_assert(r == 0, p - js, -2); p = &js[cur_token->end]; break; -- cgit v1.2.3 From 470c77fa6370bc765310638cf56256aacf963846 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Tue, 16 Nov 2010 11:53:43 +0200 Subject: Design: added assert macro and return macro. --- jsmn.c | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/jsmn.c b/jsmn.c index 3eca9b2ad..40bbdc0ac 100644 --- a/jsmn.c +++ b/jsmn.c @@ -67,12 +67,6 @@ static int jsmn_parse_primitive(const unsigned char *js, jsontok_t *token) { return -1; } -static void jsmn_error(struct jsmn_params *params, int pos) { - if (params->errpos != NULL) { - *params->errpos = pos; - } -} - static jsontok_t *jsmn_token_start(struct jsmn_params *params, jsontype_t type, int pos) { unsigned int i; jsontok_t *tokens = params->tokens; @@ -100,10 +94,17 @@ static jsontok_t *jsmn_token_end(struct jsmn_params *params, jsontype_t type, in int jsmn_parse(const unsigned char *js, jsontok_t *tokens, size_t num_tokens, int *errpos) { -#define jsmn_assert(cond, pos, err) \ - if (!(cond)) { \ - jsmn_error(¶ms, pos); \ - return (err); \ +#define jsmn_return(error) \ + do { \ + if ((errpos) != NULL) { \ + *(errpos) = (p - js); \ + } \ + return (error); \ + } while (0) + +#define jsmn_assert(cond, error) \ + if (!(cond)) { \ + jsmn_return(error); \ } struct jsmn_params params; @@ -128,31 +129,29 @@ int jsmn_parse(const unsigned char *js, jsontok_t *tokens, size_t num_tokens, in case '{': case '[': type = (*p == '{' ? JSON_OBJECT : JSON_ARRAY); cur_token = jsmn_token_start(¶ms, type, p - js); - jsmn_assert(cur_token != NULL, p - js, -1); - cur_token->start = p - js; + jsmn_assert(cur_token != NULL, -1); break; case '}' : case ']': type = (*p == '}' ? JSON_OBJECT : JSON_ARRAY); cur_token = jsmn_token_end(¶ms, type, p - js + 1); - jsmn_assert(cur_token != NULL, p - js, -1); - cur_token->end = p - js + 1; + jsmn_assert(cur_token != NULL, -1); break; case '-': case '0': case '1' : case '2': case '3' : case '4': case '5': case '6': case '7' : case '8': case '9': case 't': case 'f': case 'n' : cur_token = jsmn_token_start(¶ms, JSON_OTHER, p - js); - jsmn_assert(cur_token != NULL, p - js, -1); + jsmn_assert(cur_token != NULL, -1); r = jsmn_parse_primitive(js, cur_token); - jsmn_assert(r == 0, p - js, -2); + jsmn_assert(r == 0, -2); p = &js[cur_token->end] - 1; break; case '\"': cur_token = jsmn_token_start(¶ms, JSON_STRING, p - js); - jsmn_assert(cur_token != NULL, p - js, -1); + jsmn_assert(cur_token != NULL, -1); r = jsmn_parse_string(js, cur_token); - jsmn_assert(r == 0, p - js, -2); + jsmn_assert(r == 0, -2); p = &js[cur_token->end]; break; @@ -160,12 +159,13 @@ int jsmn_parse(const unsigned char *js, jsontok_t *tokens, size_t num_tokens, in break; default: - jsmn_error(¶ms, p - js); - return -1; + jsmn_assert(0, -1); /* Assert always fails */ } p++; } - jsmn_error(¶ms, 0); + if (errpos != NULL) *errpos = 0; return 0; +#undef jsmn_return +#undef jsmn_assert } -- cgit v1.2.3 From daf93a0ebd10e3778ec77a62577543fb11884c83 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Tue, 16 Nov 2010 11:56:08 +0200 Subject: Feature: make difference between numbers and other primitives --- jsmn.c | 6 ++++++ jsmn.h | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/jsmn.c b/jsmn.c index 40bbdc0ac..2ba4e536a 100644 --- a/jsmn.c +++ b/jsmn.c @@ -139,6 +139,12 @@ int jsmn_parse(const unsigned char *js, jsontok_t *tokens, size_t num_tokens, in case '-': case '0': case '1' : case '2': case '3' : case '4': case '5': case '6': case '7' : case '8': case '9': + cur_token = jsmn_token_start(¶ms, JSON_NUMBER, p - js); + jsmn_assert(cur_token != NULL, -1); + r = jsmn_parse_primitive(js, cur_token); + jsmn_assert(r == 0, -2); + p = &js[cur_token->end] - 1; + break; case 't': case 'f': case 'n' : cur_token = jsmn_token_start(¶ms, JSON_OTHER, p - js); jsmn_assert(cur_token != NULL, -1); diff --git a/jsmn.h b/jsmn.h index 12dd9fd0b..06f2f516a 100644 --- a/jsmn.h +++ b/jsmn.h @@ -7,7 +7,7 @@ * o Array * o String * o Number - * o Primitive: boolean (true/false) or null + * o Other primitive: boolean (true/false) or null */ typedef enum { JSON_OTHER = 0, -- cgit v1.2.3 From a2755a75953e61fffb6210c16ddbaee1e4f6ff90 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Tue, 16 Nov 2010 13:50:13 +0200 Subject: Design: some error codes added --- jsmn.c | 18 +++++++++--------- jsmn.h | 7 +++++++ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/jsmn.c b/jsmn.c index 2ba4e536a..dbf5b2fba 100644 --- a/jsmn.c +++ b/jsmn.c @@ -129,35 +129,35 @@ int jsmn_parse(const unsigned char *js, jsontok_t *tokens, size_t num_tokens, in case '{': case '[': type = (*p == '{' ? JSON_OBJECT : JSON_ARRAY); cur_token = jsmn_token_start(¶ms, type, p - js); - jsmn_assert(cur_token != NULL, -1); + jsmn_assert(cur_token != NULL, JSMN_ERROR_NOMEM); break; case '}' : case ']': type = (*p == '}' ? JSON_OBJECT : JSON_ARRAY); cur_token = jsmn_token_end(¶ms, type, p - js + 1); - jsmn_assert(cur_token != NULL, -1); + jsmn_assert(cur_token != NULL, JSMN_ERROR_PART); break; case '-': case '0': case '1' : case '2': case '3' : case '4': case '5': case '6': case '7' : case '8': case '9': cur_token = jsmn_token_start(¶ms, JSON_NUMBER, p - js); - jsmn_assert(cur_token != NULL, -1); + jsmn_assert(cur_token != NULL, JSMN_ERROR_NOMEM); r = jsmn_parse_primitive(js, cur_token); - jsmn_assert(r == 0, -2); + jsmn_assert(r == 0, JSMN_ERROR_INVAL); p = &js[cur_token->end] - 1; break; case 't': case 'f': case 'n' : cur_token = jsmn_token_start(¶ms, JSON_OTHER, p - js); - jsmn_assert(cur_token != NULL, -1); + jsmn_assert(cur_token != NULL, JSMN_ERROR_NOMEM); r = jsmn_parse_primitive(js, cur_token); - jsmn_assert(r == 0, -2); + jsmn_assert(r == 0, JSMN_ERROR_INVAL); p = &js[cur_token->end] - 1; break; case '\"': cur_token = jsmn_token_start(¶ms, JSON_STRING, p - js); - jsmn_assert(cur_token != NULL, -1); + jsmn_assert(cur_token != NULL, JSMN_ERROR_NOMEM); r = jsmn_parse_string(js, cur_token); - jsmn_assert(r == 0, -2); + jsmn_assert(r == 0, JSMN_ERROR_INVAL); p = &js[cur_token->end]; break; @@ -165,7 +165,7 @@ int jsmn_parse(const unsigned char *js, jsontok_t *tokens, size_t num_tokens, in break; default: - jsmn_assert(0, -1); /* Assert always fails */ + jsmn_return(JSMN_ERROR_INVAL); } p++; } diff --git a/jsmn.h b/jsmn.h index 06f2f516a..408c53843 100644 --- a/jsmn.h +++ b/jsmn.h @@ -17,6 +17,13 @@ typedef enum { JSON_NUMBER } jsontype_t; +typedef enum { + JSMN_ERROR_NOMEM = -1, + JSMN_ERROR_INVAL = -2, + JSMN_ERROR_PART = -3, + JSMN_SUCCESS = 0 +} jsmnerr_t; + /** * JSON token description. * @param type type (object, array, string etc.) -- cgit v1.2.3 From 60509e2850a1f4ac8324acc282bc307378d4980d Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Tue, 16 Nov 2010 15:41:49 +0200 Subject: Design: rewritten using parser structure --- Makefile | 2 +- demo.c | 11 ++-- jsmn.c | 226 ++++++++++++++++++++++++++++----------------------------------- jsmn.h | 27 ++++++-- 4 files changed, 130 insertions(+), 136 deletions(-) diff --git a/Makefile b/Makefile index cda64f530..dc90eab07 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -CFLAGS=-Wall -std=c89 +CFLAGS=-Wall -std=c89 -g OBJS=jsmn.o demo.o diff --git a/demo.c b/demo.c index a9dfa41f5..0c2d18bb5 100644 --- a/demo.c +++ b/demo.c @@ -50,7 +50,6 @@ static void jsmn_dump_obj(jsontok_t *obj, const char *js) { int main(int argc, char *argv[]) { int i; int r; - int errpos; jsontok_t tokens[NUM_TOKENS]; FILE *f; int filesize = 0; @@ -85,14 +84,18 @@ int main(int argc, char *argv[]) { fclose(f); - r = jsmn_parse((unsigned char *) js, tokens, NUM_TOKENS, &errpos); + jsmn_parser parser; + + jsmn_init_parser(&parser, js, tokens, NUM_TOKENS); + + r = jsmn_parse(&parser); if (r < 0) { - printf("error %d at pos %d: %s\n", r, errpos, &js[errpos]); + printf("error %d at pos %d: %s\n", r, parser.pos, &js[parser.pos]); exit(EXIT_FAILURE); } for (i = 0; istart] != '\"') { - return -1; - } - - /* Skip starting quote */ - token->start++; - - for (p = &js[token->start]; *p != '\0'; p++) { - /* Quote: end of string */ - if (*p == '\"') { - token->end = p - js; - return 0; - } - - /* Backslash: Quoted symbol expected */ - if (*p == '\\') { - p++; - switch (*p) { - /* Allowed escaped symbols */ - case '\"': case '/' : case '\\' : case 'b' : - case 'f' : case 'r' : case 'n' : case 't' : - break; - /* Allows escaped symbol \uXXXX */ - case 'u': - /* TODO */ - break; - /* Unexpected symbol */ - default: - return -1; - } - } - } - return -1; -} +void jsmn_init_parser(jsmn_parser *parser, const char *js, + jsontok_t *tokens, size_t num_tokens) { + unsigned int i; -static int jsmn_parse_primitive(const unsigned char *js, jsontok_t *token) { - const unsigned char *p; + parser->js = js; + parser->pos = 0; + parser->tokens = tokens; + parser->num_tokens = num_tokens; - for (p = &js[token->start]; *p != '\0'; p++) { - switch (*p) { - case '\t' : case '\r' : case '\n' : case ' ' : - case ',' : case ']' : case '}' : - token->end = p - js; - return 0; - } - if (*p < 32 || *p >= 127) { - return -1; - } + for (i = 0; i < parser->num_tokens; i++) { + parser->tokens[i].start = -1; + parser->tokens[i].end = -1; + parser->tokens[i].type = JSON_OTHER; } - return -1; } -static jsontok_t *jsmn_token_start(struct jsmn_params *params, jsontype_t type, int pos) { +jsontok_t *jsmn_start_token(jsmn_parser *parser, jsontype_t type) { unsigned int i; - jsontok_t *tokens = params->tokens; - for (i = 0; inum_tokens; i++) { + jsontok_t *tokens = parser->tokens; + for (i = 0; inum_tokens; i++) { if (tokens[i].start == -1 && tokens[i].end == -1) { - tokens[i].start = pos; + tokens[i].start = parser->pos; tokens[i].type = type; return &tokens[i]; } @@ -80,98 +31,121 @@ static jsontok_t *jsmn_token_start(struct jsmn_params *params, jsontype_t type, return NULL; } -static jsontok_t *jsmn_token_end(struct jsmn_params *params, jsontype_t type, int pos) { +jsontok_t *jsmn_end_token(jsmn_parser *parser, jsontype_t type) { int i; - jsontok_t *tokens = params->tokens; - for (i = params->num_tokens - 1; i>= 0; i--) { + jsontok_t *tokens = parser->tokens; + for (i = parser->num_tokens - 1; i>= 0; i--) { if (tokens[i].type == type && tokens[i].start != -1 && tokens[i].end == -1) { - tokens[i].end = pos; + tokens[i].end = parser->pos; return &tokens[i]; } } return NULL; } -int jsmn_parse(const unsigned char *js, jsontok_t *tokens, size_t num_tokens, int *errpos) { +static int jsmn_parse_primitive(jsmn_parser *parser) { + const char *js; + jsontok_t *token; -#define jsmn_return(error) \ - do { \ - if ((errpos) != NULL) { \ - *(errpos) = (p - js); \ - } \ - return (error); \ - } while (0) + js = parser->js; -#define jsmn_assert(cond, error) \ - if (!(cond)) { \ - jsmn_return(error); \ + token = jsmn_start_token(parser, JSON_NUMBER); + + for (; js[parser->pos] != '\0'; parser->pos++) { + switch (js[parser->pos]) { + case '\t' : case '\r' : case '\n' : case ' ' : + case ',' : case ']' : case '}' : + token->end = parser->pos; + parser->pos--; + return JSMN_SUCCESS; + } + if (js[parser->pos] < 32 || js[parser->pos] >= 127) { + return JSMN_ERROR_INVAL; + } } + return JSMN_ERROR_PART; +} - struct jsmn_params params; - int r; - unsigned int i; - const unsigned char *p; - jsontype_t type; - jsontok_t *cur_token; +static int jsmn_parse_string(jsmn_parser *parser) { + const char *js; + jsontok_t *token; + + js = parser->js; - params.num_tokens = num_tokens; - params.tokens = tokens; - params.errpos = errpos; + /* Check if string begins from a quote */ + if (js[parser->pos] != '\"') { + return JSMN_ERROR_INVAL; + } - for (i = 0; ipos++; + + token = jsmn_start_token(parser, JSON_STRING); + /* Skip starting quote */ + for (; js[parser->pos] != '\0'; parser->pos++) { + char c = js[parser->pos]; + + /* Quote: end of string */ + if (c == '\"') { + token->end = parser->pos; + return JSMN_SUCCESS; + } + + /* Backslash: Quoted symbol expected */ + if (c == '\\') { + parser->pos++; + switch (js[parser->pos]) { + /* Allowed escaped symbols */ + case '\"': case '/' : case '\\' : case 'b' : + case 'f' : case 'r' : case 'n' : case 't' : + break; + /* Allows escaped symbol \uXXXX */ + case 'u': + /* TODO */ + break; + /* Unexpected symbol */ + default: + return JSMN_ERROR_INVAL; + } + } } + return JSMN_ERROR_PART; +} + - for (p = js; *p != '\0'; ) { - switch (*p) { +jsmnerr_t jsmn_parse(jsmn_parser *parser) { + const char *js; + jsontype_t type; + jsontok_t *token; + + js = parser->js; + + for (; js[parser->pos] != '\0'; parser->pos++) { + char c; + c = js[parser->pos]; + switch (c) { case '{': case '[': - type = (*p == '{' ? JSON_OBJECT : JSON_ARRAY); - cur_token = jsmn_token_start(¶ms, type, p - js); - jsmn_assert(cur_token != NULL, JSMN_ERROR_NOMEM); + type = (c == '{' ? JSON_OBJECT : JSON_ARRAY); + token = jsmn_start_token(parser, type); break; - case '}' : case ']': - type = (*p == '}' ? JSON_OBJECT : JSON_ARRAY); - cur_token = jsmn_token_end(¶ms, type, p - js + 1); - jsmn_assert(cur_token != NULL, JSMN_ERROR_PART); + case '}': case ']': + type = (c == '}' ? JSON_OBJECT : JSON_ARRAY); + token = jsmn_end_token(parser, type); break; - case '-': case '0': case '1' : case '2': case '3' : case '4': case '5': case '6': case '7' : case '8': case '9': - cur_token = jsmn_token_start(¶ms, JSON_NUMBER, p - js); - jsmn_assert(cur_token != NULL, JSMN_ERROR_NOMEM); - r = jsmn_parse_primitive(js, cur_token); - jsmn_assert(r == 0, JSMN_ERROR_INVAL); - p = &js[cur_token->end] - 1; - break; case 't': case 'f': case 'n' : - cur_token = jsmn_token_start(¶ms, JSON_OTHER, p - js); - jsmn_assert(cur_token != NULL, JSMN_ERROR_NOMEM); - r = jsmn_parse_primitive(js, cur_token); - jsmn_assert(r == 0, JSMN_ERROR_INVAL); - p = &js[cur_token->end] - 1; + jsmn_parse_primitive(parser); break; - case '\"': - cur_token = jsmn_token_start(¶ms, JSON_STRING, p - js); - jsmn_assert(cur_token != NULL, JSMN_ERROR_NOMEM); - r = jsmn_parse_string(js, cur_token); - jsmn_assert(r == 0, JSMN_ERROR_INVAL); - p = &js[cur_token->end]; + jsmn_parse_string(parser); break; - case '\t' : case '\r' : case '\n' : case ':' : case ',': case ' ': break; - default: - jsmn_return(JSMN_ERROR_INVAL); + return JSMN_ERROR_INVAL; } - p++; } - if (errpos != NULL) *errpos = 0; - return 0; -#undef jsmn_return -#undef jsmn_assert + return JSMN_SUCCESS; } diff --git a/jsmn.h b/jsmn.h index 408c53843..f045a0853 100644 --- a/jsmn.h +++ b/jsmn.h @@ -11,10 +11,10 @@ */ typedef enum { JSON_OTHER = 0, - JSON_OBJECT, - JSON_ARRAY, - JSON_STRING, - JSON_NUMBER + JSON_OBJECT = 1, + JSON_ARRAY = 2, + JSON_STRING = 3, + JSON_NUMBER = 4 } jsontype_t; typedef enum { @@ -36,10 +36,27 @@ typedef struct { int end; } jsontok_t; +/** + * JSON parser. Contains an array of token blocks available. Also stores + * the string being parsed now and current position in that string + */ +typedef struct { + const char *js; + unsigned int pos; + size_t num_tokens; + jsontok_t *tokens; +} jsmn_parser; + +/** + * Create JSON parser over an array of tokens + */ +void jsmn_init_parser(jsmn_parser *parser, const char *js, + jsontok_t *tokens, size_t num_tokens); + /** * Run JSON parser. It parses a JSON data string into and array of tokens, each describing * a single JSON object. */ -int jsmn_parse(const unsigned char *js, jsontok_t *tokens, size_t num_tokens, int *errpos); +jsmnerr_t jsmn_parse(jsmn_parser *parser); #endif /* __JSMN_H_ */ -- cgit v1.2.3 From d0d52f68478f5cf6cb19629968dfe07c3ca040a7 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Wed, 17 Nov 2010 12:03:26 +0200 Subject: Design: primitive type implemented as a replacement to boolean/number/null. String tokens point to the unquoted string --- Makefile | 2 +- demo.c | 12 ++++-------- jsmn.c | 7 +++---- jsmn.h | 5 ++--- 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/Makefile b/Makefile index dc90eab07..53a74032d 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -CFLAGS=-Wall -std=c89 -g +CFLAGS=-Wall -std=c89 -g -O2 OBJS=jsmn.o demo.o diff --git a/demo.c b/demo.c index 0c2d18bb5..1d3294493 100644 --- a/demo.c +++ b/demo.c @@ -19,18 +19,15 @@ static void jsmn_dump_obj(jsontok_t *obj, const char *js) { len = obj->end - obj->start; - printf("[%d,%d]\t", obj->start, obj->end); + printf("[%3d,%3d]\t", obj->start, obj->end); char *type; switch (obj->type) { - case JSON_OTHER: - type = "(?)"; - break; - case JSON_NUMBER: - type = "(N)"; + case JSON_PRIMITIVE: + type = "(.)"; break; case JSON_STRING: - type = "(S)"; + type = "(s)"; break; case JSON_ARRAY: type = "(A)"; @@ -91,7 +88,6 @@ int main(int argc, char *argv[]) { r = jsmn_parse(&parser); if (r < 0) { printf("error %d at pos %d: %s\n", r, parser.pos, &js[parser.pos]); - exit(EXIT_FAILURE); } for (i = 0; inum_tokens; i++) { parser->tokens[i].start = -1; parser->tokens[i].end = -1; - parser->tokens[i].type = JSON_OTHER; + parser->tokens[i].type = JSON_PRIMITIVE; } } @@ -49,7 +49,7 @@ static int jsmn_parse_primitive(jsmn_parser *parser) { js = parser->js; - token = jsmn_start_token(parser, JSON_NUMBER); + token = jsmn_start_token(parser, JSON_PRIMITIVE); for (; js[parser->pos] != '\0'; parser->pos++) { switch (js[parser->pos]) { @@ -66,7 +66,6 @@ static int jsmn_parse_primitive(jsmn_parser *parser) { return JSMN_ERROR_PART; } - static int jsmn_parse_string(jsmn_parser *parser) { const char *js; jsontok_t *token; @@ -112,7 +111,6 @@ static int jsmn_parse_string(jsmn_parser *parser) { return JSMN_ERROR_PART; } - jsmnerr_t jsmn_parse(jsmn_parser *parser) { const char *js; jsontype_t type; @@ -131,6 +129,7 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser) { case '}': case ']': type = (c == '}' ? JSON_OBJECT : JSON_ARRAY); token = jsmn_end_token(parser, type); + token->end++; break; case '-': case '0': case '1' : case '2': case '3' : case '4': case '5': case '6': case '7' : case '8': case '9': diff --git a/jsmn.h b/jsmn.h index f045a0853..959e942d8 100644 --- a/jsmn.h +++ b/jsmn.h @@ -10,11 +10,10 @@ * o Other primitive: boolean (true/false) or null */ typedef enum { - JSON_OTHER = 0, + JSON_PRIMITIVE = 0, JSON_OBJECT = 1, JSON_ARRAY = 2, - JSON_STRING = 3, - JSON_NUMBER = 4 + JSON_STRING = 3 } jsontype_t; typedef enum { -- cgit v1.2.3 From 991ca5dd940ab9f1deb492b3c8cec4692c2f7099 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Wed, 17 Nov 2010 12:05:29 +0200 Subject: Demo: can now read from stdin --- demo.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/demo.c b/demo.c index 1d3294493..4a76436c2 100644 --- a/demo.c +++ b/demo.c @@ -57,10 +57,14 @@ int main(int argc, char *argv[]) { exit(EXIT_SUCCESS); } - f = fopen(argv[1], "r"); - if (f == NULL) { - fprintf(stderr, "Failed to open file `%s`\n", argv[1]); - exit(EXIT_FAILURE); + if (strcmp(argv[1], "-") == 0) { + f = stdin; + } else { + f = fopen(argv[1], "r"); + if (f == NULL) { + fprintf(stderr, "Failed to open file `%s`\n", argv[1]); + exit(EXIT_FAILURE); + } } while (1) { -- cgit v1.2.3 From 3922360800a704d81a33b20ab1c3c48f9bd837a2 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Wed, 17 Nov 2010 12:19:38 +0200 Subject: Demo: options added. Number of tokens can be specified now. --- demo.c | 49 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/demo.c b/demo.c index 4a76436c2..bcd601624 100644 --- a/demo.c +++ b/demo.c @@ -5,11 +5,12 @@ #include #include #include +#include +#include +#include #include "jsmn.h" -#define NUM_TOKENS 30 - static void jsmn_dump_obj(jsontok_t *obj, const char *js) { size_t len; @@ -44,29 +45,56 @@ static void jsmn_dump_obj(jsontok_t *obj, const char *js) { free(s); } +void usage(void) { + fprintf(stderr, "Usage: ./demo \n"); + exit(EXIT_SUCCESS); +} + int main(int argc, char *argv[]) { int i; int r; - jsontok_t tokens[NUM_TOKENS]; + int c; + jsontok_t *tokens; + int num_tokens = 100; FILE *f; int filesize = 0; char *js = NULL; - if (argc != 2) { - fprintf(stderr, "Usage: ./demo \n"); - exit(EXIT_SUCCESS); + while ((c = getopt(argc, argv, "ht:")) != -1) { + switch (c) { + case 'h': + usage(); + break; + case 't': + num_tokens = atoi(optarg); + if (errno || num_tokens < 0) { + fprintf(stderr, "Invalid token number: %s!\n", optarg); + exit(EXIT_FAILURE); + } + break; + } + } + + if (optind >= argc) { + usage(); } - if (strcmp(argv[1], "-") == 0) { + if (strcmp(argv[optind], "-") == 0) { f = stdin; } else { - f = fopen(argv[1], "r"); + f = fopen(argv[optind], "r"); if (f == NULL) { fprintf(stderr, "Failed to open file `%s`\n", argv[1]); exit(EXIT_FAILURE); } } + tokens = malloc(num_tokens * sizeof(jsontok_t)); + if (tokens == NULL) { + fprintf(stderr, "Cannot allocate anough memory\n"); + exit(EXIT_FAILURE); + } + while (1) { char buf[BUFSIZ]; r = fread(buf, 1, BUFSIZ, f); @@ -86,15 +114,14 @@ int main(int argc, char *argv[]) { fclose(f); jsmn_parser parser; - - jsmn_init_parser(&parser, js, tokens, NUM_TOKENS); + jsmn_init_parser(&parser, js, tokens, num_tokens); r = jsmn_parse(&parser); if (r < 0) { printf("error %d at pos %d: %s\n", r, parser.pos, &js[parser.pos]); } - for (i = 0; i Date: Wed, 17 Nov 2010 15:16:12 +0200 Subject: Token manipulation functions changes. size_t replaced with unsigned int. --- demo.c | 31 +++++++++++---------- jsmn.c | 98 ++++++++++++++++++++++++++++++++++++------------------------------ jsmn.h | 8 +++--- 3 files changed, 74 insertions(+), 63 deletions(-) diff --git a/demo.c b/demo.c index bcd601624..46f1bdd69 100644 --- a/demo.c +++ b/demo.c @@ -95,36 +95,39 @@ int main(int argc, char *argv[]) { exit(EXIT_FAILURE); } + jsmn_parser parser; + jsmn_init_parser(&parser, js, tokens, num_tokens); + while (1) { char buf[BUFSIZ]; - r = fread(buf, 1, BUFSIZ, f); + r = fread(buf, 1, 1, f); if (r <= 0) { break; } - js = (char *) realloc(js, filesize + r); + js = (char *) realloc(js, filesize + r + 1); if (js == NULL) { fprintf(stderr, "Cannot allocate anough memory\n"); fclose(f); exit(EXIT_FAILURE); } + parser.js = js; + memcpy(js + filesize, buf, r); filesize += r; - } - - fclose(f); + js[filesize] = '\0'; - jsmn_parser parser; - jsmn_init_parser(&parser, js, tokens, num_tokens); - - r = jsmn_parse(&parser); - if (r < 0) { - printf("error %d at pos %d: %s\n", r, parser.pos, &js[parser.pos]); - } + r = jsmn_parse(&parser); + if (r < 0) { + printf("error %d at pos %d: %s\n", r, parser.pos, &js[parser.pos]); + } - for (i = 0; ijs = js; - parser->pos = 0; - parser->tokens = tokens; - parser->num_tokens = num_tokens; - - for (i = 0; i < parser->num_tokens; i++) { - parser->tokens[i].start = -1; - parser->tokens[i].end = -1; - parser->tokens[i].type = JSON_PRIMITIVE; - } -} - -jsontok_t *jsmn_start_token(jsmn_parser *parser, jsontype_t type) { +static jsontok_t *jsmn_get_token(jsmn_parser *parser) { unsigned int i; jsontok_t *tokens = parser->tokens; - for (i = 0; inum_tokens; i++) { + for (i = parser->curtoken; inum_tokens; i++) { if (tokens[i].start == -1 && tokens[i].end == -1) { - tokens[i].start = parser->pos; - tokens[i].type = type; + parser->curtoken = i; return &tokens[i]; } } return NULL; } -jsontok_t *jsmn_end_token(jsmn_parser *parser, jsontype_t type) { - int i; - jsontok_t *tokens = parser->tokens; - for (i = parser->num_tokens - 1; i>= 0; i--) { - if (tokens[i].type == type && tokens[i].start != -1 && tokens[i].end == -1) { - tokens[i].end = parser->pos; - return &tokens[i]; - } +static void jsmn_fill_token(jsontok_t *token, jsontype_t type, int start, int end) { + token->type = type; + token->start = start; + token->end = end; +} + +void jsmn_init_parser(jsmn_parser *parser, const char *js, + jsontok_t *tokens, unsigned int num_tokens) { + unsigned int i; + + parser->js = js; + parser->pos = 0; + parser->tokens = tokens; + parser->num_tokens = num_tokens; + parser->curtoken = 0; + + for (i = 0; i < parser->num_tokens; i++) { + jsmn_fill_token(&parser->tokens[i], JSON_PRIMITIVE, -1, -1); } - return NULL; } static int jsmn_parse_primitive(jsmn_parser *parser) { const char *js; jsontok_t *token; + int start; - js = parser->js; + start = parser->pos; - token = jsmn_start_token(parser, JSON_PRIMITIVE); + js = parser->js; for (; js[parser->pos] != '\0'; parser->pos++) { switch (js[parser->pos]) { case '\t' : case '\r' : case '\n' : case ' ' : case ',' : case ']' : case '}' : - token->end = parser->pos; + token = jsmn_get_token(parser); + jsmn_fill_token(token, JSON_PRIMITIVE, start, parser->pos); parser->pos--; return JSMN_SUCCESS; } if (js[parser->pos] < 32 || js[parser->pos] >= 127) { + parser->pos = start; return JSMN_ERROR_INVAL; } } + parser->pos = start; return JSMN_ERROR_PART; } @@ -70,23 +66,20 @@ static int jsmn_parse_string(jsmn_parser *parser) { const char *js; jsontok_t *token; - js = parser->js; + int start = parser->pos; - /* Check if string begins from a quote */ - if (js[parser->pos] != '\"') { - return JSMN_ERROR_INVAL; - } + js = parser->js; parser->pos++; - token = jsmn_start_token(parser, JSON_STRING); /* Skip starting quote */ for (; js[parser->pos] != '\0'; parser->pos++) { char c = js[parser->pos]; /* Quote: end of string */ if (c == '\"') { - token->end = parser->pos; + token = jsmn_get_token(parser); + jsmn_fill_token(token, JSON_PRIMITIVE, start, parser->pos); return JSMN_SUCCESS; } @@ -104,14 +97,18 @@ static int jsmn_parse_string(jsmn_parser *parser) { break; /* Unexpected symbol */ default: + parser->pos = start; return JSMN_ERROR_INVAL; } } } + parser->pos = start; return JSMN_ERROR_PART; } jsmnerr_t jsmn_parse(jsmn_parser *parser) { + int r; + unsigned int i; const char *js; jsontype_t type; jsontok_t *token; @@ -123,21 +120,32 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser) { c = js[parser->pos]; switch (c) { case '{': case '[': - type = (c == '{' ? JSON_OBJECT : JSON_ARRAY); - token = jsmn_start_token(parser, type); + token = jsmn_get_token(parser); + token->type = (c == '{' ? JSON_OBJECT : JSON_ARRAY); + token->start = parser->pos; break; case '}': case ']': type = (c == '}' ? JSON_OBJECT : JSON_ARRAY); - token = jsmn_end_token(parser, type); - token->end++; + for (i = parser->curtoken; i >= 0; i--) { + token = &parser->tokens[i]; + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + token->end = parser->pos + 1; + break; + } + } break; case '-': case '0': case '1' : case '2': case '3' : case '4': case '5': case '6': case '7' : case '8': case '9': case 't': case 'f': case 'n' : - jsmn_parse_primitive(parser); + r = jsmn_parse_primitive(parser); + if (r < 0) return r; break; case '\"': - jsmn_parse_string(parser); + r = jsmn_parse_string(parser); + if (r < 0) return r; break; case '\t' : case '\r' : case '\n' : case ':' : case ',': case ' ': break; diff --git a/jsmn.h b/jsmn.h index 959e942d8..a32523706 100644 --- a/jsmn.h +++ b/jsmn.h @@ -6,8 +6,7 @@ * o Object * o Array * o String - * o Number - * o Other primitive: boolean (true/false) or null + * o Other primitive: number, boolean (true/false) or null */ typedef enum { JSON_PRIMITIVE = 0, @@ -42,7 +41,8 @@ typedef struct { typedef struct { const char *js; unsigned int pos; - size_t num_tokens; + unsigned int num_tokens; + int curtoken; jsontok_t *tokens; } jsmn_parser; @@ -50,7 +50,7 @@ typedef struct { * Create JSON parser over an array of tokens */ void jsmn_init_parser(jsmn_parser *parser, const char *js, - jsontok_t *tokens, size_t num_tokens); + jsontok_t *tokens, unsigned int num_tokens); /** * Run JSON parser. It parses a JSON data string into and array of tokens, each describing -- cgit v1.2.3 From b91dee9102c090cf7fffa089af48794e5c727c69 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Wed, 17 Nov 2010 17:33:29 +0200 Subject: Demo: declarations moved to the top of functions. --- demo.c | 43 +++++++++++++++++++------------------------ 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/demo.c b/demo.c index 46f1bdd69..671029ac8 100644 --- a/demo.c +++ b/demo.c @@ -13,6 +13,7 @@ static void jsmn_dump_obj(jsontok_t *obj, const char *js) { size_t len; + char *s; if (obj->end < 0 || obj->start < 0) { return; @@ -20,27 +21,19 @@ static void jsmn_dump_obj(jsontok_t *obj, const char *js) { len = obj->end - obj->start; - printf("[%3d,%3d]\t", obj->start, obj->end); - - char *type; - switch (obj->type) { - case JSON_PRIMITIVE: - type = "(.)"; - break; - case JSON_STRING: - type = "(s)"; - break; - case JSON_ARRAY: - type = "(A)"; - break; - case JSON_OBJECT: - type = "(O)"; - break; - } - - printf("%s ", type); - - char *s = strndup((const char *) &js[obj->start], len); + printf("[%3d,%3d] (%c)\t", obj->start, obj->end, + ({ + char c; + switch (obj->type) { + case JSON_PRIMITIVE: c = '.'; break; + case JSON_STRING: c = 's'; break; + case JSON_ARRAY: c = 'A'; break; + case JSON_OBJECT: c = 'O'; break; + default: c = '?'; + }; c; + })); + + s = strndup((const char *) &js[obj->start], len); printf("%s\n", s); free(s); } @@ -54,11 +47,14 @@ int main(int argc, char *argv[]) { int i; int r; int c; - jsontok_t *tokens; - int num_tokens = 100; + FILE *f; int filesize = 0; + + jsmn_parser parser; char *js = NULL; + jsontok_t *tokens; + int num_tokens = 100; while ((c = getopt(argc, argv, "ht:")) != -1) { switch (c) { @@ -95,7 +91,6 @@ int main(int argc, char *argv[]) { exit(EXIT_FAILURE); } - jsmn_parser parser; jsmn_init_parser(&parser, js, tokens, num_tokens); while (1) { -- cgit v1.2.3 From 8e31e4d2148febcee4ee371eadcab06aa21b83e3 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Mon, 22 Nov 2010 14:52:26 +0200 Subject: jsmn: strings start from the first char after quotes --- jsmn.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsmn.c b/jsmn.c index 85982569f..8b87b97d6 100644 --- a/jsmn.c +++ b/jsmn.c @@ -79,7 +79,7 @@ static int jsmn_parse_string(jsmn_parser *parser) { /* Quote: end of string */ if (c == '\"') { token = jsmn_get_token(parser); - jsmn_fill_token(token, JSON_PRIMITIVE, start, parser->pos); + jsmn_fill_token(token, JSON_PRIMITIVE, start+1, parser->pos); return JSMN_SUCCESS; } -- cgit v1.2.3 From 23b8487783f1fcabc164db71a9e1f5388ecb6daa Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Mon, 22 Nov 2010 14:52:46 +0200 Subject: Demo: option added to specify buffer size when reading --- demo.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/demo.c b/demo.c index 671029ac8..b0c4c92ac 100644 --- a/demo.c +++ b/demo.c @@ -54,9 +54,10 @@ int main(int argc, char *argv[]) { jsmn_parser parser; char *js = NULL; jsontok_t *tokens; + int block_size = 1024; int num_tokens = 100; - while ((c = getopt(argc, argv, "ht:")) != -1) { + while ((c = getopt(argc, argv, "ht:b:")) != -1) { switch (c) { case 'h': usage(); @@ -68,6 +69,12 @@ int main(int argc, char *argv[]) { exit(EXIT_FAILURE); } break; + case 'b': + block_size = atoi(optarg); + if (errno || block_size < 0) { + fprintf(stderr, "Invalid block size: %s!\n", optarg); + exit(EXIT_FAILURE); + } } } @@ -93,9 +100,9 @@ int main(int argc, char *argv[]) { jsmn_init_parser(&parser, js, tokens, num_tokens); + char *buf = malloc(block_size); while (1) { - char buf[BUFSIZ]; - r = fread(buf, 1, 1, f); + r = fread(buf, 1, block_size, f); if (r <= 0) { break; } @@ -122,6 +129,7 @@ int main(int argc, char *argv[]) { } fclose(f); + free(buf); free(tokens); free(js); -- cgit v1.2.3 From f88240ac2e081b94454203a2d8595b8598a037b3 Mon Sep 17 00:00:00 2001 From: Serge Zaitsev Date: Wed, 24 Nov 2010 00:26:15 +0200 Subject: Simplified demo output format --- demo.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/demo.c b/demo.c index b0c4c92ac..6508bfe50 100644 --- a/demo.c +++ b/demo.c @@ -21,7 +21,7 @@ static void jsmn_dump_obj(jsontok_t *obj, const char *js) { len = obj->end - obj->start; - printf("[%3d,%3d] (%c)\t", obj->start, obj->end, + printf("[%3d,%3d] (%c) ", obj->start, obj->end, ({ char c; switch (obj->type) { @@ -34,7 +34,11 @@ static void jsmn_dump_obj(jsontok_t *obj, const char *js) { })); s = strndup((const char *) &js[obj->start], len); - printf("%s\n", s); + char *p; + for (p = s; *p; p++) { + printf("%c", *p == '\n' ? ' ' : *p); + } + printf("\n", s); free(s); } -- cgit v1.2.3 From 508bf43fa001486025b5be6fbdc3d943e3d56ac4 Mon Sep 17 00:00:00 2001 From: Serge Zaitsev Date: Wed, 24 Nov 2010 00:54:21 +0200 Subject: Test framework implemented --- test.sh | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 test.sh diff --git a/test.sh b/test.sh new file mode 100644 index 000000000..874904744 --- /dev/null +++ b/test.sh @@ -0,0 +1,117 @@ +#!/bin/bash + +# +# Test script is organized like this: +# o two variables (PASSED and FAILED) hold the total +# number of passed/faled tests +# o expect() function performs a single test. First +# argument of the function is the variable name and +# the second is an expected value. PASSED/FAILED +# values are updated automatically +# +# Most tests look like: +# | +# | expect "varName" "expectedValue" << JSON_END +# | ..json data here... +# | JSON_END +# | +# + +PASSED=0 +FAILED=0 + +function expect() { + ret=$(./jsmn_demo -t 10 -b 256 - | grep -A 1 $1 | tail -n 1 | cut -c 15-) + if [ "x$ret" = "x$2" ]; then + PASSED=$(($PASSED+1)) + else + echo "Failed: $1 != $2" + FAILED=$(($FAILED+1)) + fi +} + +# +# TEST SET: Basic types (simple values) +# +expect 'boolVar' 'true' << JSON_END +"boolVal" : true +JSON_END + +expect 'boolVar' 'false'<< JSON_END +"boolVar" : false +JSON_END + +expect 'intVar' '12345' << JSON_END +"intVar" : 12345 +JSON_END + +expect 'floatVar' '12.345' << JSON_END +"floatVar" : 12.345 +JSON_END + +expect 'nullVar' 'null' << JSON_END +"nullVar" : null +JSON_END + +expect 'strVar' 'hello' << JSON_END +"strVar" : "hello" +JSON_END + +# +# TEST SET: Simple types (boundary values) +# + +expect 'intVar' '0' << JSON_END +"intVar" : 0 +JSON_END + +expect 'intVar' '-0' << JSON_END +"intVar" : -0 +JSON_END + +expect 'floarVar' '-0.0' << JSON_END +"floarVar" : -0.0 +JSON_END + +expect 'strVar' '\n\r\b\t \u1234' << JSON_END +"strVar" : "\n\r\b\t \u1234" +JSON_END + +expect 'strVar' '' << JSON_END +"strVar" : "" +JSON_END + +# +# TEST SET: Array types +# +expect 'arr' '[1,2,3,4,5]' << JSON_END +"arr" : [1,2,3,4,5] +JSON_END + +expect 'arr' '[1, 2.3, "4", null, true]' << JSON_END +"arr" : [1, 2.3, "4", null, true] +JSON_END + +expect 'arr' '[]' << JSON_END +"arr" : [] +JSON_END + +# +# TEST SET: Object types +# +expect 'obj' '{"a":"b"}' << JSON_END +"obj":{"a":"b"} +JSON_END + +expect 'objField' 'value' << JSON_END +{ + "foo" : "bar", + "objField" : "value" +} +JSON_END + +echo +echo "Passed: $PASSED" +echo "Failed: $FAILED" +echo + -- cgit v1.2.3 From 978ea959f41bf9653689fc3566e68bc4fc59785d Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Wed, 24 Nov 2010 11:15:21 +0200 Subject: Added test for a large number of tokens --- test.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test.sh b/test.sh index 874904744..7350222af 100644 --- a/test.sh +++ b/test.sh @@ -110,6 +110,22 @@ expect 'objField' 'value' << JSON_END } JSON_END +expect 'foo' 'bar' << JSON_END +{ + "foo" : "bar" + "a" : [ + { + "x" : "y", + "z" : "zz" + }, + 3, + false, + true, + "end" + ], +} +JSON_END + echo echo "Passed: $PASSED" echo "Failed: $FAILED" -- cgit v1.2.3 From 2cf5193d64e2b807584f05a698c1338516576991 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Mon, 29 Nov 2010 13:52:11 +0200 Subject: README changed. Now it is a template for the official web page --- README | 136 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 120 insertions(+), 16 deletions(-) diff --git a/README b/README index 1c4bd74ea..bf99296b6 100644 --- a/README +++ b/README @@ -1,25 +1,129 @@ -JSMN - Minimalistic JSON parser library -======================================= -jsmn (pronounced right as `jasmine`) is a simple and clean library for parsing -JSON (JavaScript Object Notation) data format. +JSMN +==== -You can find more information on JSON at http://www.json.org/ +jsmn (pronounced like 'jasmine') is a minimalistic JSON parser in C. It can be +easily integrated into resource-limited or embedded projects. + +You can find more information on JSON at (http://www.json.org/) Philosophy -========== +---------- + +Most JSON parsers offer you a bunch of functions to load JSON data, parse it +and extract any value by its name. jsmn proves that checking the correctness of +every JSON packet or allocating temporary objects to store parsed JSON fields +is an overkill. -jsmn will never be large and complex. This means it will never have extra -features or any bloated dependencies, besides libc. -It will always be suitable for embedded systems and other resource-limited -systems. It will always have clear API. +JSON format itself is extremely simple, so why should we complicate it? + +jsmn is designed to be **robust** (it should work fine even with erroneous +data), **fast** (it should parse data on the fly), **portable** (no unneeded +dependencies or non-standard C extensions). An of course, **simplicity** is a +key feature - simple code style, simple algorithm, simple integration. Features -======== +-------- + +* compatible with C89 +* no dependencies (even libc!) +* about 200 lines of code +* extremely small code footprint +* no dynamic memory allocation +* incremental single-pass parsing +* library code is covered with unit-tests + +Design +------ + +The rudimentary jsmn object is a **token**. + +When parsing is done, token objects contain start and end positions of JSON +token inside the JSON data block. You can just copy a corresponding range of +bytes and get token value. + +Another propetry of token is token type. It describes the type of the +corresponding JSON object. + +jsmn supports the following token types: + +* Object - a container of key-value pairs, e.g.: + `{ "foo":"bar", "x":0.3 }` +* Array - a sequence of values, e.g.: + `[ 1, 2, 3 ]` +* String - a quoted sequence of chars, e.g.: `"foo"` +* Primitive - a number, a boolean (`true`, `false`) or `null` + +jsmn doesn't handle specific JSON data types. It just points to the token +boundaries - you should parse single data fields by your own if you need this. + +Get sources +----------- + +Clone the repository (you should have mercurial installed): + + $ hg clone http://bitbucket.org/zserge/jsmn jsmn + +Repository layout it simple: jsmn.c and jsmn.h are library files; demo.c is an +example of how to use jsmn (it is also used in unit testing); test.sh is a test +script. You will also find README, LICENSE and Makefile files inside. + +API +--- + +Token types are described by `jsontype_t`: + + typedef enum { + JSON_OBJECT, + JSON_ARRAY, + JSON_STRING, + JSON_PRIMITIVE + } jsontype_t; + +**Note:** primitive tokens are not divided into numbers, booleans and null, +because one can easily tell the type using the first character: + +* 't', 'f' - boolean +* 'n' - null +* '-', '0'..'9' - number + +Tokens are described with `jsontok_t`: + + typedef struct { + jsontype_t type; + int start; + int end; + } jsontok_t; + +**Note:** string tokens point to the first character after +the opening quote and the previous symbol before final quote. This was made +to simplify string extraction from JSON data. + +All job is done by `jsmn_parser` object. You can initialize a new parser using: + + struct jsmn_parser parser; + jsmntok_t tokens[10]; + + jsmn_init_parser(&parser, js, &tokens, 10); + +This will create a parser, that can parse up to 10 JSON tokens from `js` string. + +Later, you can use `jsmn_parse(&parser)` function to process JSON string with the parser. +It something goes wrong, you will return an error. Error will be one of these: + +* `JSON_SUCCESS` - everything went fine. String was parsed +* `JSON_ERROR_INVAL` - bad token, JSON string is corrupted +* `JSON_ERROR_NOMEM` - not enough tokens, JSON string is too large +* `JSON_ERROR_PART` - JSON string is too short, it doesn't contain the whole JSON data + +If you get `JSON_ERROR_NOMEM`, you can allocate more tokens and call `jsmn_parse` once more. +If you read json data from the stream, you can call jsmn_parse and check if +return value is `JSON_ERROR_PART` to see if you have reached the end of JSON +data. + +Other info +---------- -o Just 200 lines of code -o Full C89 compatibility -o No memory allocation inside the library code -o A single pass is required to parse JSON data -o MIT license (you can include this code into your proprietary software) +This software is distributed under [MIT license](http://www.opensource.org/licenses/mit-license.php), + so feel free to integrate it in your commercial products. -- cgit v1.2.3 From 3d94cd0a184fbb319180a579323900289b29cc08 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Mon, 29 Nov 2010 13:53:13 +0200 Subject: Removed useless argument from printf --- demo.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo.c b/demo.c index 6508bfe50..8880fb7b9 100644 --- a/demo.c +++ b/demo.c @@ -38,7 +38,7 @@ static void jsmn_dump_obj(jsontok_t *obj, const char *js) { for (p = s; *p; p++) { printf("%c", *p == '\n' ? ' ' : *p); } - printf("\n", s); + printf("\n"); free(s); } -- cgit v1.2.3 From 99247345750f32468ce163e3c8a0ac39b52c65f0 Mon Sep 17 00:00:00 2001 From: Serge Zaitsev Date: Sun, 26 Dec 2010 13:02:40 +0200 Subject: Typo in tests fixed. Argument variable quoted --- test.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test.sh b/test.sh index 7350222af..4db436558 100644 --- a/test.sh +++ b/test.sh @@ -21,7 +21,7 @@ PASSED=0 FAILED=0 function expect() { - ret=$(./jsmn_demo -t 10 -b 256 - | grep -A 1 $1 | tail -n 1 | cut -c 15-) + ret=$(./jsmn_demo -t 10 -b 256 - | grep -A 1 "$1" | tail -n 1 | cut -c 15-) if [ "x$ret" = "x$2" ]; then PASSED=$(($PASSED+1)) else @@ -30,11 +30,12 @@ function expect() { fi } + # # TEST SET: Basic types (simple values) # expect 'boolVar' 'true' << JSON_END -"boolVal" : true +"boolVar" : true JSON_END expect 'boolVar' 'false'<< JSON_END -- cgit v1.2.3 From 4e29ee705f1db5cf2cfed613439a5e8b377f06d9 Mon Sep 17 00:00:00 2001 From: Serge Zaitsev Date: Sun, 26 Dec 2010 13:48:13 +0200 Subject: Fix: check if no tokens lefs, return error in that case --- jsmn.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/jsmn.c b/jsmn.c index 8b87b97d6..d983be1cf 100644 --- a/jsmn.c +++ b/jsmn.c @@ -49,6 +49,8 @@ static int jsmn_parse_primitive(jsmn_parser *parser) { case '\t' : case '\r' : case '\n' : case ' ' : case ',' : case ']' : case '}' : token = jsmn_get_token(parser); + if (token == NULL) + return JSMN_ERROR_NOMEM; jsmn_fill_token(token, JSON_PRIMITIVE, start, parser->pos); parser->pos--; return JSMN_SUCCESS; @@ -79,6 +81,8 @@ static int jsmn_parse_string(jsmn_parser *parser) { /* Quote: end of string */ if (c == '\"') { token = jsmn_get_token(parser); + if (token == NULL) + return JSMN_ERROR_NOMEM; jsmn_fill_token(token, JSON_PRIMITIVE, start+1, parser->pos); return JSMN_SUCCESS; } @@ -121,6 +125,8 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser) { switch (c) { case '{': case '[': token = jsmn_get_token(parser); + if (token == NULL) + return JSMN_ERROR_NOMEM; token->type = (c == '{' ? JSON_OBJECT : JSON_ARRAY); token->start = parser->pos; break; -- cgit v1.2.3 From c955364a952c5ad5158637b77c75edcf010d49af Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Mon, 27 Dec 2010 17:00:24 +0200 Subject: All objects renamed using jsmn prefix to keep consistency and not be mixed with other json libraries. Demo is now called just demo. --- Makefile | 19 ++++++++++++------- demo.c | 14 +++++++------- jsmn.c | 26 +++++++++++++------------- jsmn.h | 18 +++++++++--------- test.sh | 2 +- 5 files changed, 42 insertions(+), 37 deletions(-) diff --git a/Makefile b/Makefile index 53a74032d..d1f999393 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,22 @@ CFLAGS=-Wall -std=c89 -g -O2 -OBJS=jsmn.o demo.o +all: libjsmn.a -all: jsmn_demo +demo: libjsmn.a demo.o + $(CC) $(LDFLAGS) demo.o -L. -ljsmn -o $@ -jsmn_demo: $(OBJS) - $(CC) $(LDFLAGS) $(OBJS) -o $@ +libjsmn.a: jsmn.o + ar rc $@ $^ %.o: %.c jsmn.h $(CC) -c $(CFLAGS) $< -o $@ +test: all demo + sh test.sh + clean: - rm -f $(OBJS) - rm -f jsmn_demo + rm -f jsmn.o demo.o + rm -f libjsmn.a + rm -f demo -.PHONY: clean all +.PHONY: all clean test demo diff --git a/demo.c b/demo.c index 8880fb7b9..0de80fa9e 100644 --- a/demo.c +++ b/demo.c @@ -11,7 +11,7 @@ #include "jsmn.h" -static void jsmn_dump_obj(jsontok_t *obj, const char *js) { +static void jsmn_dump_obj(jsmntok_t *obj, const char *js) { size_t len; char *s; @@ -25,10 +25,10 @@ static void jsmn_dump_obj(jsontok_t *obj, const char *js) { ({ char c; switch (obj->type) { - case JSON_PRIMITIVE: c = '.'; break; - case JSON_STRING: c = 's'; break; - case JSON_ARRAY: c = 'A'; break; - case JSON_OBJECT: c = 'O'; break; + case JSMN_PRIMITIVE: c = '.'; break; + case JSMN_STRING: c = 's'; break; + case JSMN_ARRAY: c = 'A'; break; + case JSMN_OBJECT: c = 'O'; break; default: c = '?'; }; c; })); @@ -57,7 +57,7 @@ int main(int argc, char *argv[]) { jsmn_parser parser; char *js = NULL; - jsontok_t *tokens; + jsmntok_t *tokens; int block_size = 1024; int num_tokens = 100; @@ -96,7 +96,7 @@ int main(int argc, char *argv[]) { } } - tokens = malloc(num_tokens * sizeof(jsontok_t)); + tokens = malloc(num_tokens * sizeof(jsmntok_t)); if (tokens == NULL) { fprintf(stderr, "Cannot allocate anough memory\n"); exit(EXIT_FAILURE); diff --git a/jsmn.c b/jsmn.c index d983be1cf..d02c2bb6f 100644 --- a/jsmn.c +++ b/jsmn.c @@ -2,9 +2,9 @@ #include "jsmn.h" -static jsontok_t *jsmn_get_token(jsmn_parser *parser) { +static jsmntok_t *jsmn_get_token(jsmn_parser *parser) { unsigned int i; - jsontok_t *tokens = parser->tokens; + jsmntok_t *tokens = parser->tokens; for (i = parser->curtoken; inum_tokens; i++) { if (tokens[i].start == -1 && tokens[i].end == -1) { parser->curtoken = i; @@ -14,14 +14,14 @@ static jsontok_t *jsmn_get_token(jsmn_parser *parser) { return NULL; } -static void jsmn_fill_token(jsontok_t *token, jsontype_t type, int start, int end) { +static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, int start, int end) { token->type = type; token->start = start; token->end = end; } void jsmn_init_parser(jsmn_parser *parser, const char *js, - jsontok_t *tokens, unsigned int num_tokens) { + jsmntok_t *tokens, unsigned int num_tokens) { unsigned int i; parser->js = js; @@ -31,13 +31,13 @@ void jsmn_init_parser(jsmn_parser *parser, const char *js, parser->curtoken = 0; for (i = 0; i < parser->num_tokens; i++) { - jsmn_fill_token(&parser->tokens[i], JSON_PRIMITIVE, -1, -1); + jsmn_fill_token(&parser->tokens[i], JSMN_PRIMITIVE, -1, -1); } } static int jsmn_parse_primitive(jsmn_parser *parser) { const char *js; - jsontok_t *token; + jsmntok_t *token; int start; start = parser->pos; @@ -51,7 +51,7 @@ static int jsmn_parse_primitive(jsmn_parser *parser) { token = jsmn_get_token(parser); if (token == NULL) return JSMN_ERROR_NOMEM; - jsmn_fill_token(token, JSON_PRIMITIVE, start, parser->pos); + jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); parser->pos--; return JSMN_SUCCESS; } @@ -66,7 +66,7 @@ static int jsmn_parse_primitive(jsmn_parser *parser) { static int jsmn_parse_string(jsmn_parser *parser) { const char *js; - jsontok_t *token; + jsmntok_t *token; int start = parser->pos; @@ -83,7 +83,7 @@ static int jsmn_parse_string(jsmn_parser *parser) { token = jsmn_get_token(parser); if (token == NULL) return JSMN_ERROR_NOMEM; - jsmn_fill_token(token, JSON_PRIMITIVE, start+1, parser->pos); + jsmn_fill_token(token, JSMN_PRIMITIVE, start+1, parser->pos); return JSMN_SUCCESS; } @@ -114,8 +114,8 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser) { int r; unsigned int i; const char *js; - jsontype_t type; - jsontok_t *token; + jsmntype_t type; + jsmntok_t *token; js = parser->js; @@ -127,11 +127,11 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser) { token = jsmn_get_token(parser); if (token == NULL) return JSMN_ERROR_NOMEM; - token->type = (c == '{' ? JSON_OBJECT : JSON_ARRAY); + token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); token->start = parser->pos; break; case '}': case ']': - type = (c == '}' ? JSON_OBJECT : JSON_ARRAY); + type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); for (i = parser->curtoken; i >= 0; i--) { token = &parser->tokens[i]; if (token->start != -1 && token->end == -1) { diff --git a/jsmn.h b/jsmn.h index a32523706..a45569632 100644 --- a/jsmn.h +++ b/jsmn.h @@ -9,11 +9,11 @@ * o Other primitive: number, boolean (true/false) or null */ typedef enum { - JSON_PRIMITIVE = 0, - JSON_OBJECT = 1, - JSON_ARRAY = 2, - JSON_STRING = 3 -} jsontype_t; + JSMN_PRIMITIVE = 0, + JSMN_OBJECT = 1, + JSMN_ARRAY = 2, + JSMN_STRING = 3 +} jsmntype_t; typedef enum { JSMN_ERROR_NOMEM = -1, @@ -29,10 +29,10 @@ typedef enum { * @param end end position in JSON data string */ typedef struct { - jsontype_t type; + jsmntype_t type; int start; int end; -} jsontok_t; +} jsmntok_t; /** * JSON parser. Contains an array of token blocks available. Also stores @@ -43,14 +43,14 @@ typedef struct { unsigned int pos; unsigned int num_tokens; int curtoken; - jsontok_t *tokens; + jsmntok_t *tokens; } jsmn_parser; /** * Create JSON parser over an array of tokens */ void jsmn_init_parser(jsmn_parser *parser, const char *js, - jsontok_t *tokens, unsigned int num_tokens); + jsmntok_t *tokens, unsigned int num_tokens); /** * Run JSON parser. It parses a JSON data string into and array of tokens, each describing diff --git a/test.sh b/test.sh index 4db436558..05d52b8c8 100644 --- a/test.sh +++ b/test.sh @@ -21,7 +21,7 @@ PASSED=0 FAILED=0 function expect() { - ret=$(./jsmn_demo -t 10 -b 256 - | grep -A 1 "$1" | tail -n 1 | cut -c 15-) + ret=$(./demo -t 10 -b 256 - | grep -A 1 "$1" | tail -n 1 | cut -c 15-) if [ "x$ret" = "x$2" ]; then PASSED=$(($PASSED+1)) else -- cgit v1.2.3 From c4d9412483bc561cef53a784e9f28b827e010e7b Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Mon, 27 Dec 2010 17:05:22 +0200 Subject: Some comments added in json.c --- jsmn.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/jsmn.c b/jsmn.c index d02c2bb6f..3f2b149c2 100644 --- a/jsmn.c +++ b/jsmn.c @@ -2,6 +2,9 @@ #include "jsmn.h" +/** + * Allocates a fresh unused token from the token pull. + */ static jsmntok_t *jsmn_get_token(jsmn_parser *parser) { unsigned int i; jsmntok_t *tokens = parser->tokens; @@ -14,12 +17,20 @@ static jsmntok_t *jsmn_get_token(jsmn_parser *parser) { return NULL; } -static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, int start, int end) { +/** + * Fills token type and boundaries. + */ +static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, + int start, int end) { token->type = type; token->start = start; token->end = end; } +/** + * Creates a new parser based over a given buffer with an array of tokens + * available. + */ void jsmn_init_parser(jsmn_parser *parser, const char *js, jsmntok_t *tokens, unsigned int num_tokens) { unsigned int i; @@ -35,6 +46,9 @@ void jsmn_init_parser(jsmn_parser *parser, const char *js, } } +/** + * Fills next available token with JSON primitive. + */ static int jsmn_parse_primitive(jsmn_parser *parser) { const char *js; jsmntok_t *token; @@ -64,6 +78,9 @@ static int jsmn_parse_primitive(jsmn_parser *parser) { return JSMN_ERROR_PART; } +/** + * Filsl next token with JSON string. + */ static int jsmn_parse_string(jsmn_parser *parser) { const char *js; jsmntok_t *token; @@ -110,6 +127,9 @@ static int jsmn_parse_string(jsmn_parser *parser) { return JSMN_ERROR_PART; } +/** + * Parse JSON string and fill tokens. + */ jsmnerr_t jsmn_parse(jsmn_parser *parser) { int r; unsigned int i; -- cgit v1.2.3 From 4e869f7e9e3121ee84b8a3b65806143f7c8672ab Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Tue, 28 Dec 2010 11:16:41 +0200 Subject: Complex types (objects and arrays) now have also size - number of child elements --- demo.c | 2 +- jsmn.c | 19 ++++++++++++++++++- jsmn.h | 2 ++ test.sh | 2 +- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/demo.c b/demo.c index 0de80fa9e..1f6cac742 100644 --- a/demo.c +++ b/demo.c @@ -21,7 +21,7 @@ static void jsmn_dump_obj(jsmntok_t *obj, const char *js) { len = obj->end - obj->start; - printf("[%3d,%3d] (%c) ", obj->start, obj->end, + printf("[%3d,%3d - %2d] (%c) ", obj->start, obj->end, obj->size, ({ char c; switch (obj->type) { diff --git a/jsmn.c b/jsmn.c index 3f2b149c2..9907e27ea 100644 --- a/jsmn.c +++ b/jsmn.c @@ -11,6 +11,7 @@ static jsmntok_t *jsmn_get_token(jsmn_parser *parser) { for (i = parser->curtoken; inum_tokens; i++) { if (tokens[i].start == -1 && tokens[i].end == -1) { parser->curtoken = i; + tokens[i].size = 0; return &tokens[i]; } } @@ -40,6 +41,7 @@ void jsmn_init_parser(jsmn_parser *parser, const char *js, parser->tokens = tokens; parser->num_tokens = num_tokens; parser->curtoken = 0; + parser->cursize = NULL; for (i = 0; i < parser->num_tokens; i++) { jsmn_fill_token(&parser->tokens[i], JSMN_PRIMITIVE, -1, -1); @@ -132,7 +134,7 @@ static int jsmn_parse_string(jsmn_parser *parser) { */ jsmnerr_t jsmn_parse(jsmn_parser *parser) { int r; - unsigned int i; + int i; const char *js; jsmntype_t type; jsmntok_t *token; @@ -147,8 +149,11 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser) { token = jsmn_get_token(parser); if (token == NULL) return JSMN_ERROR_NOMEM; + if (parser->cursize != NULL) + (*parser->cursize)++; token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); token->start = parser->pos; + parser->cursize = &token->size; break; case '}': case ']': type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); @@ -158,20 +163,32 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser) { if (token->type != type) { return JSMN_ERROR_INVAL; } + parser->cursize = NULL; token->end = parser->pos + 1; break; } } + for (; i >= 0; i--) { + token = &parser->tokens[i]; + if (token->start != -1 && token->end == -1) { + parser->cursize = &token->size; + break; + } + } break; case '-': case '0': case '1' : case '2': case '3' : case '4': case '5': case '6': case '7' : case '8': case '9': case 't': case 'f': case 'n' : r = jsmn_parse_primitive(parser); if (r < 0) return r; + if (parser->cursize != NULL) + (*parser->cursize)++; break; case '\"': r = jsmn_parse_string(parser); if (r < 0) return r; + if (parser->cursize != NULL) + (*parser->cursize)++; break; case '\t' : case '\r' : case '\n' : case ':' : case ',': case ' ': break; diff --git a/jsmn.h b/jsmn.h index a45569632..8b21cab4d 100644 --- a/jsmn.h +++ b/jsmn.h @@ -32,6 +32,7 @@ typedef struct { jsmntype_t type; int start; int end; + int size; } jsmntok_t; /** @@ -43,6 +44,7 @@ typedef struct { unsigned int pos; unsigned int num_tokens; int curtoken; + int *cursize; jsmntok_t *tokens; } jsmn_parser; diff --git a/test.sh b/test.sh index 05d52b8c8..d6dbcf9db 100644 --- a/test.sh +++ b/test.sh @@ -21,7 +21,7 @@ PASSED=0 FAILED=0 function expect() { - ret=$(./demo -t 10 -b 256 - | grep -A 1 "$1" | tail -n 1 | cut -c 15-) + ret=$(./demo -t 10 -b 256 - | grep -A 1 "$1" | tail -n 1 | cut -c 20-) if [ "x$ret" = "x$2" ]; then PASSED=$(($PASSED+1)) else -- cgit v1.2.3 From 42be9208f75cec544346c430e8811b359048e639 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Tue, 28 Dec 2010 12:06:52 +0200 Subject: README updated --- README | 108 ++++++++++++++++++++++++++++++++++++++++------------------------- 1 file changed, 66 insertions(+), 42 deletions(-) diff --git a/README b/README index bf99296b6..57da6a1cd 100644 --- a/README +++ b/README @@ -5,7 +5,7 @@ JSMN jsmn (pronounced like 'jasmine') is a minimalistic JSON parser in C. It can be easily integrated into resource-limited or embedded projects. -You can find more information on JSON at (http://www.json.org/) +You can find more information about JSON format at (http://www.json.org/) Philosophy ---------- @@ -13,22 +13,25 @@ Philosophy Most JSON parsers offer you a bunch of functions to load JSON data, parse it and extract any value by its name. jsmn proves that checking the correctness of every JSON packet or allocating temporary objects to store parsed JSON fields -is an overkill. +often is an overkill. JSON format itself is extremely simple, so why should we complicate it? jsmn is designed to be **robust** (it should work fine even with erroneous -data), **fast** (it should parse data on the fly), **portable** (no unneeded +data), **fast** (it should parse data on the fly), **portable** (no superfluous dependencies or non-standard C extensions). An of course, **simplicity** is a -key feature - simple code style, simple algorithm, simple integration. +key feature - simple code style, simple algorithm, simple integration into +other projects. Features -------- * compatible with C89 * no dependencies (even libc!) +* highly portable (tested on x86/amd64, ARM, AVR) * about 200 lines of code * extremely small code footprint +* API contains only 2 functions * no dynamic memory allocation * incremental single-pass parsing * library code is covered with unit-tests @@ -36,16 +39,22 @@ Features Design ------ -The rudimentary jsmn object is a **token**. +The rudimentary jsmn object is a **token**. Let's consider a JSON string: -When parsing is done, token objects contain start and end positions of JSON -token inside the JSON data block. You can just copy a corresponding range of -bytes and get token value. + '{ "name" : "Jack", "age" : 27 }' -Another propetry of token is token type. It describes the type of the -corresponding JSON object. +It holds the following tokens: -jsmn supports the following token types: +* Object: `{ "name" : "Jack", "age" : 27}` (the whole object) +* Strings: `"name"`, `"Jack"`, `"age"` (keys and some values) +* Number: `27` + +In jsmn, tokens do not hold any data, but point to token boundaries in JSON +string instead. In the example above jsmn will create tokens like: Object +[0..31], String [3..7], String [12..16], String [20..23], Number [27..29]. + +Every jsmn token has a type, which indicates the type of corresponding JSON +token. jsmn supports the following token types: * Object - a container of key-value pairs, e.g.: `{ "foo":"bar", "x":0.3 }` @@ -54,46 +63,58 @@ jsmn supports the following token types: * String - a quoted sequence of chars, e.g.: `"foo"` * Primitive - a number, a boolean (`true`, `false`) or `null` -jsmn doesn't handle specific JSON data types. It just points to the token -boundaries - you should parse single data fields by your own if you need this. +Besides start/end positions, jsmn tokens for complex types (like arrays +or objects) also contain a number of child items, so you can easily follow +object hierarchy. + +This approach provides enough information for parsing any JSON data and makes +it possible to use zero-copy techniques. -Get sources ------------ +Install +------- -Clone the repository (you should have mercurial installed): +To clone the repository you should have mercurial installed. Just run: $ hg clone http://bitbucket.org/zserge/jsmn jsmn -Repository layout it simple: jsmn.c and jsmn.h are library files; demo.c is an -example of how to use jsmn (it is also used in unit testing); test.sh is a test +Repository layout is simple: jsmn.c and jsmn.h are library files; demo.c is an +example of how to use jsmn (it is also used in unit tests); test.sh is a test script. You will also find README, LICENSE and Makefile files inside. +To build the library, run `make`. It is also recommended to run `make test`. +Let me know, if some tests fail. + +If build was successful, you should get a `libjsmn.a` library. +The header file you should include is called `"jsmn.h"`. + API --- -Token types are described by `jsontype_t`: +Token types are described by `jsmntype_t`: typedef enum { - JSON_OBJECT, - JSON_ARRAY, - JSON_STRING, - JSON_PRIMITIVE - } jsontype_t; + JSMN_OBJECT, + JSMN_ARRAY, + JSMN_STRING, + JSMN_PRIMITIVE + } jsmntype_t; -**Note:** primitive tokens are not divided into numbers, booleans and null, -because one can easily tell the type using the first character: +**Note:** Unlike JSON data types, primitive tokens are not divided into +numbers, booleans and null, because one can easily tell the type using the +first character: * 't', 'f' - boolean * 'n' - null * '-', '0'..'9' - number -Tokens are described with `jsontok_t`: +Token is an object of `jsmntok_t` type: typedef struct { - jsontype_t type; - int start; - int end; - } jsontok_t; + jsmntype_t type; // Token type + int start; // Token start position + int end; // Token end position + int size; // Number of child (nested) tokens + } jsmntok_t; **Note:** string tokens point to the first character after the opening quote and the previous symbol before final quote. This was made @@ -103,23 +124,26 @@ All job is done by `jsmn_parser` object. You can initialize a new parser using: struct jsmn_parser parser; jsmntok_t tokens[10]; - - jsmn_init_parser(&parser, js, &tokens, 10); + + // js - pointer to JSON string + // tokens - an array of tokens available + // 10 - number of tokens available + jsmn_init_parser(&parser, js, tokens, 10); This will create a parser, that can parse up to 10 JSON tokens from `js` string. Later, you can use `jsmn_parse(&parser)` function to process JSON string with the parser. -It something goes wrong, you will return an error. Error will be one of these: +If something goes wrong, you will get an error. Error will be one of these: -* `JSON_SUCCESS` - everything went fine. String was parsed -* `JSON_ERROR_INVAL` - bad token, JSON string is corrupted -* `JSON_ERROR_NOMEM` - not enough tokens, JSON string is too large -* `JSON_ERROR_PART` - JSON string is too short, it doesn't contain the whole JSON data +* `JSMN_SUCCESS` - everything went fine. String was parsed +* `JSMN_ERROR_INVAL` - bad token, JSON string is corrupted +* `JSMN_ERROR_NOMEM` - not enough tokens, JSON string is too large +* `JSMN_ERROR_PART` - JSON string is too short, expecting more JSON data -If you get `JSON_ERROR_NOMEM`, you can allocate more tokens and call `jsmn_parse` once more. -If you read json data from the stream, you can call jsmn_parse and check if -return value is `JSON_ERROR_PART` to see if you have reached the end of JSON -data. +If you get `JSON_ERROR_NOMEM`, you can re-allocate more tokens and call +`jsmn_parse` once more. If you read json data from the stream, you can +periodically call `jsmn_parse` and check if return value is `JSON_ERROR_PART`. +You will get this error until you reach the end of JSON data. Other info ---------- -- cgit v1.2.3 From d6f48a6a3d11bd2acc23897674a9ab4a36ecb50b Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Mon, 28 Mar 2011 13:32:44 +0300 Subject: fixed: issue #1, thanks to m_einman for his patch --- jsmn.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsmn.c b/jsmn.c index 9907e27ea..2761ee290 100644 --- a/jsmn.c +++ b/jsmn.c @@ -102,7 +102,7 @@ static int jsmn_parse_string(jsmn_parser *parser) { token = jsmn_get_token(parser); if (token == NULL) return JSMN_ERROR_NOMEM; - jsmn_fill_token(token, JSMN_PRIMITIVE, start+1, parser->pos); + jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos); return JSMN_SUCCESS; } -- cgit v1.2.3 From d6209011a5ff65c4ea0e135ddef5e54e40edfa56 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Thu, 15 Sep 2011 18:43:48 +0300 Subject: added download links in README --- README | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README b/README index 57da6a1cd..c6e7686f0 100644 --- a/README +++ b/README @@ -5,7 +5,9 @@ JSMN jsmn (pronounced like 'jasmine') is a minimalistic JSON parser in C. It can be easily integrated into resource-limited or embedded projects. -You can find more information about JSON format at (http://www.json.org/) +You can find more information about JSON format at [json.org][1] + +Library sources are available at [bitbucket.org/zserge/jsmn][2] Philosophy ---------- @@ -151,3 +153,5 @@ Other info This software is distributed under [MIT license](http://www.opensource.org/licenses/mit-license.php), so feel free to integrate it in your commercial products. +[1]: http://www.json.org/ +[2]: https://bitbucket.org/zserge/jsmn/wiki/Home -- cgit v1.2.3 From 0c22b772ed03ff960c2e052b2422a28b855b0927 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Mon, 30 Jan 2012 14:03:36 +0200 Subject: using system AR program, build options are moved to config.mk --- Makefile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index d1f999393..35b7ac622 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ -CFLAGS=-Wall -std=c89 -g -O2 +# You can put your build options here +-include config.mk all: libjsmn.a @@ -6,7 +7,7 @@ demo: libjsmn.a demo.o $(CC) $(LDFLAGS) demo.o -L. -ljsmn -o $@ libjsmn.a: jsmn.o - ar rc $@ $^ + $(AR) rc $@ $^ %.o: %.c jsmn.h $(CC) -c $(CFLAGS) $< -o $@ @@ -20,3 +21,4 @@ clean: rm -f demo .PHONY: all clean test demo + -- cgit v1.2.3 From 2928f7ec0ebcd6ae9937a5689d8da2369c863f69 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Wed, 1 Feb 2012 12:38:05 +0200 Subject: renamed: get_token to alloc_token --- jsmn.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jsmn.c b/jsmn.c index 2761ee290..04404a7a6 100644 --- a/jsmn.c +++ b/jsmn.c @@ -5,7 +5,7 @@ /** * Allocates a fresh unused token from the token pull. */ -static jsmntok_t *jsmn_get_token(jsmn_parser *parser) { +static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser) { unsigned int i; jsmntok_t *tokens = parser->tokens; for (i = parser->curtoken; inum_tokens; i++) { @@ -64,7 +64,7 @@ static int jsmn_parse_primitive(jsmn_parser *parser) { switch (js[parser->pos]) { case '\t' : case '\r' : case '\n' : case ' ' : case ',' : case ']' : case '}' : - token = jsmn_get_token(parser); + token = jsmn_alloc_token(parser); if (token == NULL) return JSMN_ERROR_NOMEM; jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); @@ -99,7 +99,7 @@ static int jsmn_parse_string(jsmn_parser *parser) { /* Quote: end of string */ if (c == '\"') { - token = jsmn_get_token(parser); + token = jsmn_alloc_token(parser); if (token == NULL) return JSMN_ERROR_NOMEM; jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos); @@ -146,7 +146,7 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser) { c = js[parser->pos]; switch (c) { case '{': case '[': - token = jsmn_get_token(parser); + token = jsmn_alloc_token(parser); if (token == NULL) return JSMN_ERROR_NOMEM; if (parser->cursize != NULL) -- cgit v1.2.3 From 4b5c5ed66ac35f50b6c172a712883acb5f0cc62f Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Wed, 1 Feb 2012 12:38:51 +0200 Subject: switched to C unit-tests instead of bash --- Makefile | 11 +++-- demo.c | 141 ------------------------------------------------------------ jsmn_test.c | 57 ++++++++++++++++++++++++ test.sh | 134 --------------------------------------------------------- 4 files changed, 64 insertions(+), 279 deletions(-) delete mode 100644 demo.c create mode 100644 jsmn_test.c delete mode 100644 test.sh diff --git a/Makefile b/Makefile index 35b7ac622..bb9337a9d 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,8 @@ all: libjsmn.a -demo: libjsmn.a demo.o - $(CC) $(LDFLAGS) demo.o -L. -ljsmn -o $@ +#demo: libjsmn.a demo.o +# $(CC) $(LDFLAGS) demo.o -L. -ljsmn -o $@ libjsmn.a: jsmn.o $(AR) rc $@ $^ @@ -12,8 +12,11 @@ libjsmn.a: jsmn.o %.o: %.c jsmn.h $(CC) -c $(CFLAGS) $< -o $@ -test: all demo - sh test.sh +test: jsmn_test + ./jsmn_test + +jsmn_test: jsmn_test.o + $(CC) -L. -ljsmn $< -o $@ clean: rm -f jsmn.o demo.o diff --git a/demo.c b/demo.c deleted file mode 100644 index 1f6cac742..000000000 --- a/demo.c +++ /dev/null @@ -1,141 +0,0 @@ -/* This demo is not needed to be C89-compatible, so for now GCC extensions are - * used */ -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include - -#include "jsmn.h" - -static void jsmn_dump_obj(jsmntok_t *obj, const char *js) { - size_t len; - char *s; - - if (obj->end < 0 || obj->start < 0) { - return; - } - - len = obj->end - obj->start; - - printf("[%3d,%3d - %2d] (%c) ", obj->start, obj->end, obj->size, - ({ - char c; - switch (obj->type) { - case JSMN_PRIMITIVE: c = '.'; break; - case JSMN_STRING: c = 's'; break; - case JSMN_ARRAY: c = 'A'; break; - case JSMN_OBJECT: c = 'O'; break; - default: c = '?'; - }; c; - })); - - s = strndup((const char *) &js[obj->start], len); - char *p; - for (p = s; *p; p++) { - printf("%c", *p == '\n' ? ' ' : *p); - } - printf("\n"); - free(s); -} - -void usage(void) { - fprintf(stderr, "Usage: ./demo \n"); - exit(EXIT_SUCCESS); -} - -int main(int argc, char *argv[]) { - int i; - int r; - int c; - - FILE *f; - int filesize = 0; - - jsmn_parser parser; - char *js = NULL; - jsmntok_t *tokens; - int block_size = 1024; - int num_tokens = 100; - - while ((c = getopt(argc, argv, "ht:b:")) != -1) { - switch (c) { - case 'h': - usage(); - break; - case 't': - num_tokens = atoi(optarg); - if (errno || num_tokens < 0) { - fprintf(stderr, "Invalid token number: %s!\n", optarg); - exit(EXIT_FAILURE); - } - break; - case 'b': - block_size = atoi(optarg); - if (errno || block_size < 0) { - fprintf(stderr, "Invalid block size: %s!\n", optarg); - exit(EXIT_FAILURE); - } - } - } - - if (optind >= argc) { - usage(); - } - - if (strcmp(argv[optind], "-") == 0) { - f = stdin; - } else { - f = fopen(argv[optind], "r"); - if (f == NULL) { - fprintf(stderr, "Failed to open file `%s`\n", argv[1]); - exit(EXIT_FAILURE); - } - } - - tokens = malloc(num_tokens * sizeof(jsmntok_t)); - if (tokens == NULL) { - fprintf(stderr, "Cannot allocate anough memory\n"); - exit(EXIT_FAILURE); - } - - jsmn_init_parser(&parser, js, tokens, num_tokens); - - char *buf = malloc(block_size); - while (1) { - r = fread(buf, 1, block_size, f); - if (r <= 0) { - break; - } - js = (char *) realloc(js, filesize + r + 1); - if (js == NULL) { - fprintf(stderr, "Cannot allocate anough memory\n"); - fclose(f); - exit(EXIT_FAILURE); - } - parser.js = js; - - memcpy(js + filesize, buf, r); - filesize += r; - js[filesize] = '\0'; - - r = jsmn_parse(&parser); - if (r < 0) { - printf("error %d at pos %d: %s\n", r, parser.pos, &js[parser.pos]); - } - - for (i = 0; i + +#include "jsmn.c" + +static int test_passed = 0; +static int test_failed = 0; + +/* Terminate current test with error */ +#define fail() return __LINE__ + +/* Successfull end of the test case */ +#define done() return 0 + +/* Check single condition */ +#define check(cond) do { if (!(cond)) fail(); } while (0) + +/* Test runner */ +static void test(int (*func)(void), const char *name) { + int r = func(); + if (r == 0) { + test_passed++; + } else { + test_failed++; + printf("FAILED: %s (at line %d)\n", name, r); + } +} + +#define TOKEN_EQ(t, tok_start, tok_end, tok_type) \ + ((t).start == tok_start \ + && (t).end == tok_end \ + && (t).type == (tok_type)) + +#define TOKEN_PRINT(t) \ + printf("start: %d, end: %d, type: %d\n", (t).start, (t).end, (t).type) + + + +int test_primitive() { + int r; + jsmn_parser p; + jsmntok_t tokens[10]; + + jsmn_init_parser(&p, "{\"a\": 0}", tokens, 10); + + r = jsmn_parse(&p); + check(r == JSMN_SUCCESS); + check(TOKEN_EQ(tokens[0], 0, 8, JSMN_OBJECT)); + check(TOKEN_EQ(tokens[1], 2, 3, JSMN_STRING)); + check(TOKEN_EQ(tokens[2], 6, 7, JSMN_PRIMITIVE)); + + return 0; +} + +int main() { + test(test_primitive, "test primitive values"); + return 0; +} diff --git a/test.sh b/test.sh deleted file mode 100644 index d6dbcf9db..000000000 --- a/test.sh +++ /dev/null @@ -1,134 +0,0 @@ -#!/bin/bash - -# -# Test script is organized like this: -# o two variables (PASSED and FAILED) hold the total -# number of passed/faled tests -# o expect() function performs a single test. First -# argument of the function is the variable name and -# the second is an expected value. PASSED/FAILED -# values are updated automatically -# -# Most tests look like: -# | -# | expect "varName" "expectedValue" << JSON_END -# | ..json data here... -# | JSON_END -# | -# - -PASSED=0 -FAILED=0 - -function expect() { - ret=$(./demo -t 10 -b 256 - | grep -A 1 "$1" | tail -n 1 | cut -c 20-) - if [ "x$ret" = "x$2" ]; then - PASSED=$(($PASSED+1)) - else - echo "Failed: $1 != $2" - FAILED=$(($FAILED+1)) - fi -} - - -# -# TEST SET: Basic types (simple values) -# -expect 'boolVar' 'true' << JSON_END -"boolVar" : true -JSON_END - -expect 'boolVar' 'false'<< JSON_END -"boolVar" : false -JSON_END - -expect 'intVar' '12345' << JSON_END -"intVar" : 12345 -JSON_END - -expect 'floatVar' '12.345' << JSON_END -"floatVar" : 12.345 -JSON_END - -expect 'nullVar' 'null' << JSON_END -"nullVar" : null -JSON_END - -expect 'strVar' 'hello' << JSON_END -"strVar" : "hello" -JSON_END - -# -# TEST SET: Simple types (boundary values) -# - -expect 'intVar' '0' << JSON_END -"intVar" : 0 -JSON_END - -expect 'intVar' '-0' << JSON_END -"intVar" : -0 -JSON_END - -expect 'floarVar' '-0.0' << JSON_END -"floarVar" : -0.0 -JSON_END - -expect 'strVar' '\n\r\b\t \u1234' << JSON_END -"strVar" : "\n\r\b\t \u1234" -JSON_END - -expect 'strVar' '' << JSON_END -"strVar" : "" -JSON_END - -# -# TEST SET: Array types -# -expect 'arr' '[1,2,3,4,5]' << JSON_END -"arr" : [1,2,3,4,5] -JSON_END - -expect 'arr' '[1, 2.3, "4", null, true]' << JSON_END -"arr" : [1, 2.3, "4", null, true] -JSON_END - -expect 'arr' '[]' << JSON_END -"arr" : [] -JSON_END - -# -# TEST SET: Object types -# -expect 'obj' '{"a":"b"}' << JSON_END -"obj":{"a":"b"} -JSON_END - -expect 'objField' 'value' << JSON_END -{ - "foo" : "bar", - "objField" : "value" -} -JSON_END - -expect 'foo' 'bar' << JSON_END -{ - "foo" : "bar" - "a" : [ - { - "x" : "y", - "z" : "zz" - }, - 3, - false, - true, - "end" - ], -} -JSON_END - -echo -echo "Passed: $PASSED" -echo "Failed: $FAILED" -echo - -- cgit v1.2.3 From bed0a7a3e647abb7204d73c69f9f44b37bdfc84f Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Wed, 1 Feb 2012 13:56:06 +0200 Subject: changed API: parse now is more flexible, but init jsut resets the parser; added new test macro to compate strings, fixed Makefile --- Makefile | 9 +++------ jsmn.c | 42 +++++++++++++++++++----------------------- jsmn.h | 10 +++++++--- jsmn_test.c | 27 +++++++++++++++++++++++---- 4 files changed, 52 insertions(+), 36 deletions(-) diff --git a/Makefile b/Makefile index bb9337a9d..f89758102 100644 --- a/Makefile +++ b/Makefile @@ -3,9 +3,6 @@ all: libjsmn.a -#demo: libjsmn.a demo.o -# $(CC) $(LDFLAGS) demo.o -L. -ljsmn -o $@ - libjsmn.a: jsmn.o $(AR) rc $@ $^ @@ -19,9 +16,9 @@ jsmn_test: jsmn_test.o $(CC) -L. -ljsmn $< -o $@ clean: - rm -f jsmn.o demo.o + rm -f jsmn.o jsmn_test.o + rm -f jsmn_test rm -f libjsmn.a - rm -f demo -.PHONY: all clean test demo +.PHONY: all clean test diff --git a/jsmn.c b/jsmn.c index 04404a7a6..5554c8f8f 100644 --- a/jsmn.c +++ b/jsmn.c @@ -28,26 +28,6 @@ static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, token->end = end; } -/** - * Creates a new parser based over a given buffer with an array of tokens - * available. - */ -void jsmn_init_parser(jsmn_parser *parser, const char *js, - jsmntok_t *tokens, unsigned int num_tokens) { - unsigned int i; - - parser->js = js; - parser->pos = 0; - parser->tokens = tokens; - parser->num_tokens = num_tokens; - parser->curtoken = 0; - parser->cursize = NULL; - - for (i = 0; i < parser->num_tokens; i++) { - jsmn_fill_token(&parser->tokens[i], JSMN_PRIMITIVE, -1, -1); - } -} - /** * Fills next available token with JSON primitive. */ @@ -132,14 +112,20 @@ static int jsmn_parse_string(jsmn_parser *parser) { /** * Parse JSON string and fill tokens. */ -jsmnerr_t jsmn_parse(jsmn_parser *parser) { +jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, + unsigned int num_tokens) { int r; int i; - const char *js; jsmntype_t type; jsmntok_t *token; - js = parser->js; + /* initialize the rest of tokens (they could be reallocated) */ + parser->num_tokens = num_tokens; + parser->tokens = tokens; + parser->js = js; + for (i = parser->curtoken; i < parser->num_tokens; i++) { + jsmn_fill_token(&parser->tokens[i], JSMN_PRIMITIVE, -1, -1); + } for (; js[parser->pos] != '\0'; parser->pos++) { char c; @@ -199,3 +185,13 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser) { return JSMN_SUCCESS; } +/** + * Creates a new parser based over a given buffer with an array of tokens + * available. + */ +void jsmn_init(jsmn_parser *parser) { + parser->pos = 0; + parser->curtoken = 0; + parser->cursize = NULL; +} + diff --git a/jsmn.h b/jsmn.h index 8b21cab4d..57112fa8c 100644 --- a/jsmn.h +++ b/jsmn.h @@ -16,9 +16,13 @@ typedef enum { } jsmntype_t; typedef enum { + /* Not enough tokens were provided */ JSMN_ERROR_NOMEM = -1, + /* Invalid character inside JSON string */ JSMN_ERROR_INVAL = -2, + /* The string is not a full JSON packet, more bytes expected */ JSMN_ERROR_PART = -3, + /* Everything was fine */ JSMN_SUCCESS = 0 } jsmnerr_t; @@ -51,13 +55,13 @@ typedef struct { /** * Create JSON parser over an array of tokens */ -void jsmn_init_parser(jsmn_parser *parser, const char *js, - jsmntok_t *tokens, unsigned int num_tokens); +void jsmn_init(jsmn_parser *parser); /** * Run JSON parser. It parses a JSON data string into and array of tokens, each describing * a single JSON object. */ -jsmnerr_t jsmn_parse(jsmn_parser *parser); +jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, + jsmntok_t *tokens, unsigned int num_tokens); #endif /* __JSMN_H_ */ diff --git a/jsmn_test.c b/jsmn_test.c index a6e042944..0f706738d 100644 --- a/jsmn_test.c +++ b/jsmn_test.c @@ -1,4 +1,6 @@ #include +#include +#include #include "jsmn.c" @@ -30,28 +32,45 @@ static void test(int (*func)(void), const char *name) { && (t).end == tok_end \ && (t).type == (tok_type)) +#define TOKEN_STIRNG(js, t, s) \ + (strncmp(js+(t).start, s, (t).end - (t).start) == 0 \ + && strlen(s) == (t).end - (t).start) + #define TOKEN_PRINT(t) \ printf("start: %d, end: %d, type: %d\n", (t).start, (t).end, (t).type) -int test_primitive() { +int test_simple() { + const char *js; int r; jsmn_parser p; jsmntok_t tokens[10]; - jsmn_init_parser(&p, "{\"a\": 0}", tokens, 10); + js = "{\"a\": 0}"; - r = jsmn_parse(&p); + jsmn_init(&p); + r = jsmn_parse(&p, js, tokens, 10); check(r == JSMN_SUCCESS); check(TOKEN_EQ(tokens[0], 0, 8, JSMN_OBJECT)); check(TOKEN_EQ(tokens[1], 2, 3, JSMN_STRING)); check(TOKEN_EQ(tokens[2], 6, 7, JSMN_PRIMITIVE)); + check(TOKEN_STIRNG(js, tokens[0], js)); + check(TOKEN_STIRNG(js, tokens[1], "a")); + check(TOKEN_STIRNG(js, tokens[2], "0")); + return 0; } +int test_primitive() { + jsmn_parser p; + jsmntok_t tokens[10]; + const char *js; +} + int main() { - test(test_primitive, "test primitive values"); + test(test_simple, "general test for a simple JSON string"); return 0; } + -- cgit v1.2.3 From 443be365f6bf8639a1b1e836e8e948d12af28100 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Wed, 1 Feb 2012 14:02:12 +0200 Subject: removed JSON string and token array from parser internals, because they must not be saved between parse() calls --- jsmn.c | 39 ++++++++++++++++----------------------- jsmn.h | 3 --- jsmn_test.c | 2 ++ 3 files changed, 18 insertions(+), 26 deletions(-) diff --git a/jsmn.c b/jsmn.c index 5554c8f8f..dd3a33c5a 100644 --- a/jsmn.c +++ b/jsmn.c @@ -5,10 +5,10 @@ /** * Allocates a fresh unused token from the token pull. */ -static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser) { +static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, + jsmntok_t *tokens, size_t num_tokens) { unsigned int i; - jsmntok_t *tokens = parser->tokens; - for (i = parser->curtoken; inum_tokens; i++) { + for (i = parser->curtoken; i < num_tokens; i++) { if (tokens[i].start == -1 && tokens[i].end == -1) { parser->curtoken = i; tokens[i].size = 0; @@ -31,20 +31,18 @@ static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, /** * Fills next available token with JSON primitive. */ -static int jsmn_parse_primitive(jsmn_parser *parser) { - const char *js; +static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, + jsmntok_t *tokens, size_t num_tokens) { jsmntok_t *token; int start; start = parser->pos; - js = parser->js; - for (; js[parser->pos] != '\0'; parser->pos++) { switch (js[parser->pos]) { case '\t' : case '\r' : case '\n' : case ' ' : case ',' : case ']' : case '}' : - token = jsmn_alloc_token(parser); + token = jsmn_alloc_token(parser, tokens, num_tokens); if (token == NULL) return JSMN_ERROR_NOMEM; jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); @@ -63,14 +61,12 @@ static int jsmn_parse_primitive(jsmn_parser *parser) { /** * Filsl next token with JSON string. */ -static int jsmn_parse_string(jsmn_parser *parser) { - const char *js; +static int jsmn_parse_string(jsmn_parser *parser, const char *js, + jsmntok_t *tokens, size_t num_tokens) { jsmntok_t *token; int start = parser->pos; - js = parser->js; - parser->pos++; /* Skip starting quote */ @@ -79,7 +75,7 @@ static int jsmn_parse_string(jsmn_parser *parser) { /* Quote: end of string */ if (c == '\"') { - token = jsmn_alloc_token(parser); + token = jsmn_alloc_token(parser, tokens, num_tokens); if (token == NULL) return JSMN_ERROR_NOMEM; jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos); @@ -120,11 +116,8 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, jsmntok_t *token; /* initialize the rest of tokens (they could be reallocated) */ - parser->num_tokens = num_tokens; - parser->tokens = tokens; - parser->js = js; - for (i = parser->curtoken; i < parser->num_tokens; i++) { - jsmn_fill_token(&parser->tokens[i], JSMN_PRIMITIVE, -1, -1); + for (i = parser->curtoken; i < num_tokens; i++) { + jsmn_fill_token(&tokens[i], JSMN_PRIMITIVE, -1, -1); } for (; js[parser->pos] != '\0'; parser->pos++) { @@ -132,7 +125,7 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, c = js[parser->pos]; switch (c) { case '{': case '[': - token = jsmn_alloc_token(parser); + token = jsmn_alloc_token(parser, tokens, num_tokens); if (token == NULL) return JSMN_ERROR_NOMEM; if (parser->cursize != NULL) @@ -144,7 +137,7 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, case '}': case ']': type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); for (i = parser->curtoken; i >= 0; i--) { - token = &parser->tokens[i]; + token = &tokens[i]; if (token->start != -1 && token->end == -1) { if (token->type != type) { return JSMN_ERROR_INVAL; @@ -155,7 +148,7 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, } } for (; i >= 0; i--) { - token = &parser->tokens[i]; + token = &tokens[i]; if (token->start != -1 && token->end == -1) { parser->cursize = &token->size; break; @@ -165,13 +158,13 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, case '-': case '0': case '1' : case '2': case '3' : case '4': case '5': case '6': case '7' : case '8': case '9': case 't': case 'f': case 'n' : - r = jsmn_parse_primitive(parser); + r = jsmn_parse_primitive(parser, js, tokens, num_tokens); if (r < 0) return r; if (parser->cursize != NULL) (*parser->cursize)++; break; case '\"': - r = jsmn_parse_string(parser); + r = jsmn_parse_string(parser, js, tokens, num_tokens); if (r < 0) return r; if (parser->cursize != NULL) (*parser->cursize)++; diff --git a/jsmn.h b/jsmn.h index 57112fa8c..3d1f9ec20 100644 --- a/jsmn.h +++ b/jsmn.h @@ -44,12 +44,9 @@ typedef struct { * the string being parsed now and current position in that string */ typedef struct { - const char *js; unsigned int pos; - unsigned int num_tokens; int curtoken; int *cursize; - jsmntok_t *tokens; } jsmn_parser; /** diff --git a/jsmn_test.c b/jsmn_test.c index 0f706738d..7c7e31018 100644 --- a/jsmn_test.c +++ b/jsmn_test.c @@ -67,10 +67,12 @@ int test_primitive() { jsmn_parser p; jsmntok_t tokens[10]; const char *js; + return 0; } int main() { test(test_simple, "general test for a simple JSON string"); + test(test_primitive, "test primitive JSON data types"); return 0; } -- cgit v1.2.3 From 929e2337562c21d17b4ee31b1db90ccf0fa09d8d Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Wed, 1 Feb 2012 16:03:36 +0200 Subject: added tests for primitive types, primitive types now can be stored outside the objects without braces --- jsmn.c | 21 ++++++++++++++------- jsmn_test.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 8 deletions(-) diff --git a/jsmn.c b/jsmn.c index dd3a33c5a..22757b221 100644 --- a/jsmn.c +++ b/jsmn.c @@ -42,20 +42,26 @@ static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, switch (js[parser->pos]) { case '\t' : case '\r' : case '\n' : case ' ' : case ',' : case ']' : case '}' : - token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) - return JSMN_ERROR_NOMEM; - jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); - parser->pos--; - return JSMN_SUCCESS; + goto found; } if (js[parser->pos] < 32 || js[parser->pos] >= 127) { parser->pos = start; return JSMN_ERROR_INVAL; } } + /* TODO: CHECK THIS ONLY WHEN IN JSON STRICT MODE */ +#if 0 parser->pos = start; return JSMN_ERROR_PART; +#endif + +found: + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) + return JSMN_ERROR_NOMEM; + jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); + parser->pos--; + return JSMN_SUCCESS; } /** @@ -112,7 +118,6 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, unsigned int num_tokens) { int r; int i; - jsmntype_t type; jsmntok_t *token; /* initialize the rest of tokens (they could be reallocated) */ @@ -122,6 +127,8 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, for (; js[parser->pos] != '\0'; parser->pos++) { char c; + jsmntype_t type; + c = js[parser->pos]; switch (c) { case '{': case '[': diff --git a/jsmn_test.c b/jsmn_test.c index 7c7e31018..6a0b3407e 100644 --- a/jsmn_test.c +++ b/jsmn_test.c @@ -64,15 +64,66 @@ int test_simple() { } int test_primitive() { + int r; jsmn_parser p; - jsmntok_t tokens[10]; + jsmntok_t tok[10]; const char *js; + + js = "\"boolVar\" : true"; + jsmn_init(&p); + r = jsmn_parse(&p, js, tok, 10); + check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING + && tok[1].type == JSMN_PRIMITIVE); + check(TOKEN_STIRNG(js, tok[0], "boolVar")); + check(TOKEN_STIRNG(js, tok[1], "true")); + + js = "\"boolVar\" : false"; + jsmn_init(&p); + r = jsmn_parse(&p, js, tok, 10); + check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING + && tok[1].type == JSMN_PRIMITIVE); + check(TOKEN_STIRNG(js, tok[0], "boolVar")); + check(TOKEN_STIRNG(js, tok[1], "false")); + + js = "\"intVar\" : 12345"; + jsmn_init(&p); + r = jsmn_parse(&p, js, tok, 10); + check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING + && tok[1].type == JSMN_PRIMITIVE); + check(TOKEN_STIRNG(js, tok[0], "intVar")); + check(TOKEN_STIRNG(js, tok[1], "12345")); + + js = "\"floatVar\" : 12.345"; + jsmn_init(&p); + r = jsmn_parse(&p, js, tok, 10); + check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING + && tok[1].type == JSMN_PRIMITIVE); + check(TOKEN_STIRNG(js, tok[0], "floatVar")); + check(TOKEN_STIRNG(js, tok[1], "12.345")); + + js = "\"nullVar\" : null"; + jsmn_init(&p); + r = jsmn_parse(&p, js, tok, 10); + check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING + && tok[1].type == JSMN_PRIMITIVE); + check(TOKEN_STIRNG(js, tok[0], "nullVar")); + check(TOKEN_STIRNG(js, tok[1], "null")); + + js = "\"strVar\" : \"hello world\""; + jsmn_init(&p); + r = jsmn_parse(&p, js, tok, 10); + check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING + && tok[1].type == JSMN_STRING); + check(TOKEN_STIRNG(js, tok[0], "strVar")); + check(TOKEN_STIRNG(js, tok[1], "hello world")); + return 0; } int main() { test(test_simple, "general test for a simple JSON string"); test(test_primitive, "test primitive JSON data types"); + printf("\nPASSED: %d\nFAILED: %d\n", test_passed, test_failed); return 0; } -- cgit v1.2.3 From 30f932772ecd749f43b9e9fda18626d3895e4c1c Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Wed, 1 Feb 2012 17:15:54 +0200 Subject: tested partial parser for strings, added primitive string parser test, fixed token allocation --- Makefile | 2 ++ jsmn.c | 10 +++++----- jsmn.h | 2 +- jsmn_test.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index f89758102..0afdef4fd 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,8 @@ test: jsmn_test jsmn_test: jsmn_test.o $(CC) -L. -ljsmn $< -o $@ +jsmn_test.o: jsmn_test.c libjsmn.a + clean: rm -f jsmn.o jsmn_test.o rm -f jsmn_test diff --git a/jsmn.c b/jsmn.c index 22757b221..1bf561423 100644 --- a/jsmn.c +++ b/jsmn.c @@ -8,9 +8,9 @@ static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens, size_t num_tokens) { unsigned int i; - for (i = parser->curtoken; i < num_tokens; i++) { + for (i = parser->toknext; i < num_tokens; i++) { if (tokens[i].start == -1 && tokens[i].end == -1) { - parser->curtoken = i; + parser->toknext = i + 1; tokens[i].size = 0; return &tokens[i]; } @@ -121,7 +121,7 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, jsmntok_t *token; /* initialize the rest of tokens (they could be reallocated) */ - for (i = parser->curtoken; i < num_tokens; i++) { + for (i = parser->toknext; i < num_tokens; i++) { jsmn_fill_token(&tokens[i], JSMN_PRIMITIVE, -1, -1); } @@ -143,7 +143,7 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, break; case '}': case ']': type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); - for (i = parser->curtoken; i >= 0; i--) { + for (i = parser->toknext - 1; i >= 0; i--) { token = &tokens[i]; if (token->start != -1 && token->end == -1) { if (token->type != type) { @@ -191,7 +191,7 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, */ void jsmn_init(jsmn_parser *parser) { parser->pos = 0; - parser->curtoken = 0; + parser->toknext = 0; parser->cursize = NULL; } diff --git a/jsmn.h b/jsmn.h index 3d1f9ec20..72040bc3b 100644 --- a/jsmn.h +++ b/jsmn.h @@ -45,7 +45,7 @@ typedef struct { */ typedef struct { unsigned int pos; - int curtoken; + int toknext; int *cursize; } jsmn_parser; diff --git a/jsmn_test.c b/jsmn_test.c index 6a0b3407e..b01e6c21e 100644 --- a/jsmn_test.c +++ b/jsmn_test.c @@ -109,6 +109,15 @@ int test_primitive() { check(TOKEN_STIRNG(js, tok[0], "nullVar")); check(TOKEN_STIRNG(js, tok[1], "null")); + return 0; +} + +int test_string() { + int r; + jsmn_parser p; + jsmntok_t tok[10]; + const char *js; + js = "\"strVar\" : \"hello world\""; jsmn_init(&p); r = jsmn_parse(&p, js, tok, 10); @@ -117,12 +126,61 @@ int test_primitive() { check(TOKEN_STIRNG(js, tok[0], "strVar")); check(TOKEN_STIRNG(js, tok[1], "hello world")); + js = "\"strVar\" : \"escapes: \\/\\r\\n\\t\\b\\f\\\"\\\\\""; + jsmn_init(&p); + r = jsmn_parse(&p, js, tok, 10); + check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING + && tok[1].type == JSMN_STRING); + check(TOKEN_STIRNG(js, tok[0], "strVar")); + check(TOKEN_STIRNG(js, tok[1], "escapes: \\/\\r\\n\\t\\b\\f\\\"\\\\")); + + js = "\"strVar\" : \"\""; + jsmn_init(&p); + r = jsmn_parse(&p, js, tok, 10); + check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING + && tok[1].type == JSMN_STRING); + check(TOKEN_STIRNG(js, tok[0], "strVar")); + check(TOKEN_STIRNG(js, tok[1], "")); + + return 0; +} + +int test_partial_string() { + int r; + jsmn_parser p; + jsmntok_t tok[10]; + const char *js; + + jsmn_init(&p); + js = "\"x\": \"va"; + r = jsmn_parse(&p, js, tok, 10); + check(r == JSMN_ERROR_PART && tok[0].type == JSMN_STRING); + check(TOKEN_STIRNG(js, tok[0], "x")); + check(TOKEN_EQ(tok[1], -1, -1, 0)); + + js = "\"x\": \"valu"; + r = jsmn_parse(&p, js, tok, 10); + check(r == JSMN_ERROR_PART && tok[0].type == JSMN_STRING); + check(TOKEN_STIRNG(js, tok[0], "x")); + check(TOKEN_EQ(tok[1], -1, -1, 0)); + + js = "\"x\": \"value\""; + r = jsmn_parse(&p, js, tok, 10); + check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING + && tok[1].type == JSMN_STRING); + check(TOKEN_STIRNG(js, tok[0], "x")); + check(TOKEN_STIRNG(js, tok[1], "value")); + return 0; } int main() { +#if 0 test(test_simple, "general test for a simple JSON string"); test(test_primitive, "test primitive JSON data types"); + test(test_string, "test string JSON data types"); +#endif + test(test_partial_string, "test partial JSON string parsing"); printf("\nPASSED: %d\nFAILED: %d\n", test_passed, test_failed); return 0; } -- cgit v1.2.3 From e395ad562e83e6db07b88cb595cb676f6ffe8420 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Wed, 1 Feb 2012 17:18:32 +0200 Subject: added one more test case for partial string parser --- jsmn_test.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/jsmn_test.c b/jsmn_test.c index b01e6c21e..17019c8aa 100644 --- a/jsmn_test.c +++ b/jsmn_test.c @@ -171,6 +171,16 @@ int test_partial_string() { check(TOKEN_STIRNG(js, tok[0], "x")); check(TOKEN_STIRNG(js, tok[1], "value")); + js = "\"x\": \"value\", \"y\": \"value y\""; + r = jsmn_parse(&p, js, tok, 10); + check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING + && tok[1].type == JSMN_STRING && tok[2].type == JSMN_STRING + && tok[3].type == JSMN_STRING); + check(TOKEN_STIRNG(js, tok[0], "x")); + check(TOKEN_STIRNG(js, tok[1], "value")); + check(TOKEN_STIRNG(js, tok[2], "y")); + check(TOKEN_STIRNG(js, tok[3], "value y")); + return 0; } -- cgit v1.2.3 From 41171ecd5129abf2b6a933c2cb81e6795963f1a3 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Wed, 1 Feb 2012 17:51:42 +0200 Subject: fixed typo, added JSMN_STRICT mode --- jsmn.c | 36 ++++++++++++++++++--------- jsmn_test.c | 81 ++++++++++++++++++++++++++++++++++++++----------------------- 2 files changed, 76 insertions(+), 41 deletions(-) diff --git a/jsmn.c b/jsmn.c index 1bf561423..443465c5c 100644 --- a/jsmn.c +++ b/jsmn.c @@ -40,7 +40,10 @@ static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, for (; js[parser->pos] != '\0'; parser->pos++) { switch (js[parser->pos]) { - case '\t' : case '\r' : case '\n' : case ' ' : +#ifndef JSMN_STRICT + /* In strict mode primitive must be followed by "," or "}" or "]" */ + case '\t' : case '\r' : case '\n' : case ' ' : case ':': +#endif case ',' : case ']' : case '}' : goto found; } @@ -49,8 +52,8 @@ static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, return JSMN_ERROR_INVAL; } } - /* TODO: CHECK THIS ONLY WHEN IN JSON STRICT MODE */ -#if 0 +#ifdef JSMN_STRICT + /* In strict mode primitive must be followed by a comma/object/array */ parser->pos = start; return JSMN_ERROR_PART; #endif @@ -162,14 +165,6 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, } } break; - case '-': case '0': case '1' : case '2': case '3' : case '4': - case '5': case '6': case '7' : case '8': case '9': - case 't': case 'f': case 'n' : - r = jsmn_parse_primitive(parser, js, tokens, num_tokens); - if (r < 0) return r; - if (parser->cursize != NULL) - (*parser->cursize)++; - break; case '\"': r = jsmn_parse_string(parser, js, tokens, num_tokens); if (r < 0) return r; @@ -178,8 +173,27 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, break; case '\t' : case '\r' : case '\n' : case ':' : case ',': case ' ': break; +#ifdef JSMN_STRICT + /* In strict mode primitives are: numbers and booleans */ + case '-': case '0': case '1' : case '2': case '3' : case '4': + case '5': case '6': case '7' : case '8': case '9': + case 't': case 'f': case 'n' : +#else + /* In non-strict mode every unquoted value is a primitive */ + default: +#endif + r = jsmn_parse_primitive(parser, js, tokens, num_tokens); + if (r < 0) return r; + if (parser->cursize != NULL) + (*parser->cursize)++; + break; + +#ifdef JSMN_STRICT + /* Unexpected char in strict mode */ default: return JSMN_ERROR_INVAL; +#endif + } } return JSMN_SUCCESS; diff --git a/jsmn_test.c b/jsmn_test.c index 17019c8aa..2efa80273 100644 --- a/jsmn_test.c +++ b/jsmn_test.c @@ -32,7 +32,7 @@ static void test(int (*func)(void), const char *name) { && (t).end == tok_end \ && (t).type == (tok_type)) -#define TOKEN_STIRNG(js, t, s) \ +#define TOKEN_STRING(js, t, s) \ (strncmp(js+(t).start, s, (t).end - (t).start) == 0 \ && strlen(s) == (t).end - (t).start) @@ -56,9 +56,9 @@ int test_simple() { check(TOKEN_EQ(tokens[1], 2, 3, JSMN_STRING)); check(TOKEN_EQ(tokens[2], 6, 7, JSMN_PRIMITIVE)); - check(TOKEN_STIRNG(js, tokens[0], js)); - check(TOKEN_STIRNG(js, tokens[1], "a")); - check(TOKEN_STIRNG(js, tokens[2], "0")); + check(TOKEN_STRING(js, tokens[0], js)); + check(TOKEN_STRING(js, tokens[1], "a")); + check(TOKEN_STRING(js, tokens[2], "0")); return 0; } @@ -74,40 +74,40 @@ int test_primitive() { r = jsmn_parse(&p, js, tok, 10); check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING && tok[1].type == JSMN_PRIMITIVE); - check(TOKEN_STIRNG(js, tok[0], "boolVar")); - check(TOKEN_STIRNG(js, tok[1], "true")); + check(TOKEN_STRING(js, tok[0], "boolVar")); + check(TOKEN_STRING(js, tok[1], "true")); js = "\"boolVar\" : false"; jsmn_init(&p); r = jsmn_parse(&p, js, tok, 10); check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING && tok[1].type == JSMN_PRIMITIVE); - check(TOKEN_STIRNG(js, tok[0], "boolVar")); - check(TOKEN_STIRNG(js, tok[1], "false")); + check(TOKEN_STRING(js, tok[0], "boolVar")); + check(TOKEN_STRING(js, tok[1], "false")); js = "\"intVar\" : 12345"; jsmn_init(&p); r = jsmn_parse(&p, js, tok, 10); check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING && tok[1].type == JSMN_PRIMITIVE); - check(TOKEN_STIRNG(js, tok[0], "intVar")); - check(TOKEN_STIRNG(js, tok[1], "12345")); + check(TOKEN_STRING(js, tok[0], "intVar")); + check(TOKEN_STRING(js, tok[1], "12345")); js = "\"floatVar\" : 12.345"; jsmn_init(&p); r = jsmn_parse(&p, js, tok, 10); check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING && tok[1].type == JSMN_PRIMITIVE); - check(TOKEN_STIRNG(js, tok[0], "floatVar")); - check(TOKEN_STIRNG(js, tok[1], "12.345")); + check(TOKEN_STRING(js, tok[0], "floatVar")); + check(TOKEN_STRING(js, tok[1], "12.345")); js = "\"nullVar\" : null"; jsmn_init(&p); r = jsmn_parse(&p, js, tok, 10); check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING && tok[1].type == JSMN_PRIMITIVE); - check(TOKEN_STIRNG(js, tok[0], "nullVar")); - check(TOKEN_STIRNG(js, tok[1], "null")); + check(TOKEN_STRING(js, tok[0], "nullVar")); + check(TOKEN_STRING(js, tok[1], "null")); return 0; } @@ -123,24 +123,24 @@ int test_string() { r = jsmn_parse(&p, js, tok, 10); check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING && tok[1].type == JSMN_STRING); - check(TOKEN_STIRNG(js, tok[0], "strVar")); - check(TOKEN_STIRNG(js, tok[1], "hello world")); + check(TOKEN_STRING(js, tok[0], "strVar")); + check(TOKEN_STRING(js, tok[1], "hello world")); js = "\"strVar\" : \"escapes: \\/\\r\\n\\t\\b\\f\\\"\\\\\""; jsmn_init(&p); r = jsmn_parse(&p, js, tok, 10); check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING && tok[1].type == JSMN_STRING); - check(TOKEN_STIRNG(js, tok[0], "strVar")); - check(TOKEN_STIRNG(js, tok[1], "escapes: \\/\\r\\n\\t\\b\\f\\\"\\\\")); + check(TOKEN_STRING(js, tok[0], "strVar")); + check(TOKEN_STRING(js, tok[1], "escapes: \\/\\r\\n\\t\\b\\f\\\"\\\\")); js = "\"strVar\" : \"\""; jsmn_init(&p); r = jsmn_parse(&p, js, tok, 10); check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING && tok[1].type == JSMN_STRING); - check(TOKEN_STIRNG(js, tok[0], "strVar")); - check(TOKEN_STIRNG(js, tok[1], "")); + check(TOKEN_STRING(js, tok[0], "strVar")); + check(TOKEN_STRING(js, tok[1], "")); return 0; } @@ -155,42 +155,63 @@ int test_partial_string() { js = "\"x\": \"va"; r = jsmn_parse(&p, js, tok, 10); check(r == JSMN_ERROR_PART && tok[0].type == JSMN_STRING); - check(TOKEN_STIRNG(js, tok[0], "x")); + check(TOKEN_STRING(js, tok[0], "x")); check(TOKEN_EQ(tok[1], -1, -1, 0)); js = "\"x\": \"valu"; r = jsmn_parse(&p, js, tok, 10); check(r == JSMN_ERROR_PART && tok[0].type == JSMN_STRING); - check(TOKEN_STIRNG(js, tok[0], "x")); + check(TOKEN_STRING(js, tok[0], "x")); check(TOKEN_EQ(tok[1], -1, -1, 0)); js = "\"x\": \"value\""; r = jsmn_parse(&p, js, tok, 10); check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING && tok[1].type == JSMN_STRING); - check(TOKEN_STIRNG(js, tok[0], "x")); - check(TOKEN_STIRNG(js, tok[1], "value")); + check(TOKEN_STRING(js, tok[0], "x")); + check(TOKEN_STRING(js, tok[1], "value")); js = "\"x\": \"value\", \"y\": \"value y\""; r = jsmn_parse(&p, js, tok, 10); check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING && tok[1].type == JSMN_STRING && tok[2].type == JSMN_STRING && tok[3].type == JSMN_STRING); - check(TOKEN_STIRNG(js, tok[0], "x")); - check(TOKEN_STIRNG(js, tok[1], "value")); - check(TOKEN_STIRNG(js, tok[2], "y")); - check(TOKEN_STIRNG(js, tok[3], "value y")); + check(TOKEN_STRING(js, tok[0], "x")); + check(TOKEN_STRING(js, tok[1], "value")); + check(TOKEN_STRING(js, tok[2], "y")); + check(TOKEN_STRING(js, tok[3], "value y")); return 0; } +int test_unquoted_keys() { +#ifndef JSMN_STRICT + int r; + jsmn_parser p; + jsmntok_t tok[10]; + const char *js; + + jsmn_init(&p); + js = "key1: \"value\"\nkey2 : 123"; + + r = jsmn_parse(&p, js, tok, 10); + check(r == JSMN_SUCCESS && tok[0].type == JSMN_PRIMITIVE + && tok[1].type == JSMN_STRING && tok[2].type == JSMN_PRIMITIVE + && tok[3].type == JSMN_PRIMITIVE); + check(TOKEN_STRING(js, tok[0], "key1")); + check(TOKEN_STRING(js, tok[1], "value")); + check(TOKEN_STRING(js, tok[2], "key2")); + check(TOKEN_STRING(js, tok[3], "123")); +#endif + return 0; +} + int main() { -#if 0 test(test_simple, "general test for a simple JSON string"); test(test_primitive, "test primitive JSON data types"); test(test_string, "test string JSON data types"); -#endif test(test_partial_string, "test partial JSON string parsing"); + test(test_unquoted_keys, "test unquoted keys (like in JavaScript)"); printf("\nPASSED: %d\nFAILED: %d\n", test_passed, test_failed); return 0; } -- cgit v1.2.3 From 53454e542929d07b6fea438fd112eed2f7d865b4 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Thu, 2 Feb 2012 11:40:36 +0200 Subject: added test for partial array reading --- jsmn.c | 8 ++++++++ jsmn_test.c | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/jsmn.c b/jsmn.c index 443465c5c..cca3bbcbf 100644 --- a/jsmn.c +++ b/jsmn.c @@ -196,6 +196,14 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, } } + + for (i = parser->toknext - 1; i >= 0; i--) { + /* Unmatched opened object or array */ + if (tokens[i].start != -1 && tokens[i].end == -1) { + return JSMN_ERROR_PART; + } + } + return JSMN_SUCCESS; } diff --git a/jsmn_test.c b/jsmn_test.c index 2efa80273..81238e71a 100644 --- a/jsmn_test.c +++ b/jsmn_test.c @@ -206,11 +206,50 @@ int test_unquoted_keys() { return 0; } +int test_partial_array() { + int r; + jsmn_parser p; + jsmntok_t tok[10]; + const char *js; + + jsmn_init(&p); + js = " [ 1, true, "; + r = jsmn_parse(&p, js, tok, 10); + check(r == JSMN_ERROR_PART && tok[0].type == JSMN_ARRAY + && tok[1].type == JSMN_PRIMITIVE && tok[2].type == JSMN_PRIMITIVE); + + js = " [ 1, true, [123, \"hello"; + r = jsmn_parse(&p, js, tok, 10); + check(r == JSMN_ERROR_PART && tok[0].type == JSMN_ARRAY + && tok[1].type == JSMN_PRIMITIVE && tok[2].type == JSMN_PRIMITIVE + && tok[3].type == JSMN_ARRAY && tok[4].type == JSMN_PRIMITIVE); + + js = " [ 1, true, [123, \"hello\"]"; + r = jsmn_parse(&p, js, tok, 10); + check(r == JSMN_ERROR_PART && tok[0].type == JSMN_ARRAY + && tok[1].type == JSMN_PRIMITIVE && tok[2].type == JSMN_PRIMITIVE + && tok[3].type == JSMN_ARRAY && tok[4].type == JSMN_PRIMITIVE + && tok[5].type == JSMN_STRING); + /* check child nodes of the 2nd array */ + check(tok[3].size == 2); + + js = " [ 1, true, [123, \"hello\"]]"; + r = jsmn_parse(&p, js, tok, 10); + check(r == JSMN_SUCCESS && tok[0].type == JSMN_ARRAY + && tok[1].type == JSMN_PRIMITIVE && tok[2].type == JSMN_PRIMITIVE + && tok[3].type == JSMN_ARRAY && tok[4].type == JSMN_PRIMITIVE + && tok[5].type == JSMN_STRING); + check(tok[3].size == 2); + check(tok[0].size == 3); + return 0; +} + int main() { test(test_simple, "general test for a simple JSON string"); test(test_primitive, "test primitive JSON data types"); test(test_string, "test string JSON data types"); test(test_partial_string, "test partial JSON string parsing"); + test(test_partial_array, "test partial array reading"); test(test_unquoted_keys, "test unquoted keys (like in JavaScript)"); printf("\nPASSED: %d\nFAILED: %d\n", test_passed, test_failed); return 0; -- cgit v1.2.3 From a983a7606136a3dfd538a9940c5ebf48d200251c Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Thu, 2 Feb 2012 13:15:22 +0200 Subject: added reading with small number of tokens test, fixed NOMEM issue #2 --- jsmn.c | 8 ++++++-- jsmn_test.c | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/jsmn.c b/jsmn.c index cca3bbcbf..ef53cc0d2 100644 --- a/jsmn.c +++ b/jsmn.c @@ -60,8 +60,10 @@ static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, found: token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) + if (token == NULL) { + parser->pos = start; return JSMN_ERROR_NOMEM; + } jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); parser->pos--; return JSMN_SUCCESS; @@ -85,8 +87,10 @@ static int jsmn_parse_string(jsmn_parser *parser, const char *js, /* Quote: end of string */ if (c == '\"') { token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) + if (token == NULL) { + parser->pos = start; return JSMN_ERROR_NOMEM; + } jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos); return JSMN_SUCCESS; } diff --git a/jsmn_test.c b/jsmn_test.c index 81238e71a..a356a9527 100644 --- a/jsmn_test.c +++ b/jsmn_test.c @@ -244,12 +244,36 @@ int test_partial_array() { return 0; } +int test_array_nomem() { + int i; + int r; + jsmn_parser p; + jsmntok_t toksmall[10], toklarge[10]; + const char *js; + + js = " [ 1, true, [123, \"hello\"]]"; + + for (i = 0; i < 6; i++) { + printf("i = %d\n", i); + jsmn_init(&p); + r = jsmn_parse(&p, js, toksmall, i); + check(r == JSMN_ERROR_NOMEM); + + memcpy(toklarge, toksmall, sizeof(toksmall)); + + r = jsmn_parse(&p, js, toklarge, 10); + check(r == JSMN_SUCCESS); + } + return 0; +} + int main() { test(test_simple, "general test for a simple JSON string"); test(test_primitive, "test primitive JSON data types"); test(test_string, "test string JSON data types"); test(test_partial_string, "test partial JSON string parsing"); test(test_partial_array, "test partial array reading"); + test(test_array_nomem, "test array reading with a smaller number of tokens"); test(test_unquoted_keys, "test unquoted keys (like in JavaScript)"); printf("\nPASSED: %d\nFAILED: %d\n", test_passed, test_failed); return 0; -- cgit v1.2.3 From f3b41ae30c7627f540ee539aed51bcca06d23a82 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Thu, 2 Feb 2012 13:26:15 +0200 Subject: fixed: superior node size is now an index, not a pointer (safe to realloc) --- jsmn.c | 22 +++++++++++----------- jsmn.h | 6 +++--- jsmn_test.c | 6 +++++- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/jsmn.c b/jsmn.c index ef53cc0d2..e38a47252 100644 --- a/jsmn.c +++ b/jsmn.c @@ -11,7 +11,6 @@ static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, for (i = parser->toknext; i < num_tokens; i++) { if (tokens[i].start == -1 && tokens[i].end == -1) { parser->toknext = i + 1; - tokens[i].size = 0; return &tokens[i]; } } @@ -26,6 +25,7 @@ static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, token->type = type; token->start = start; token->end = end; + token->size = 0; } /** @@ -142,11 +142,11 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, token = jsmn_alloc_token(parser, tokens, num_tokens); if (token == NULL) return JSMN_ERROR_NOMEM; - if (parser->cursize != NULL) - (*parser->cursize)++; + if (parser->toksuper != -1) + tokens[parser->toksuper].size++; token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); token->start = parser->pos; - parser->cursize = &token->size; + parser->toksuper = parser->toknext - 1; break; case '}': case ']': type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); @@ -156,7 +156,7 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, if (token->type != type) { return JSMN_ERROR_INVAL; } - parser->cursize = NULL; + parser->toksuper = -1; token->end = parser->pos + 1; break; } @@ -164,7 +164,7 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, for (; i >= 0; i--) { token = &tokens[i]; if (token->start != -1 && token->end == -1) { - parser->cursize = &token->size; + parser->toksuper = i; break; } } @@ -172,8 +172,8 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, case '\"': r = jsmn_parse_string(parser, js, tokens, num_tokens); if (r < 0) return r; - if (parser->cursize != NULL) - (*parser->cursize)++; + if (parser->toksuper != -1) + tokens[parser->toksuper].size++; break; case '\t' : case '\r' : case '\n' : case ':' : case ',': case ' ': break; @@ -188,8 +188,8 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, #endif r = jsmn_parse_primitive(parser, js, tokens, num_tokens); if (r < 0) return r; - if (parser->cursize != NULL) - (*parser->cursize)++; + if (parser->toksuper != -1) + tokens[parser->toksuper].size++; break; #ifdef JSMN_STRICT @@ -218,6 +218,6 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, void jsmn_init(jsmn_parser *parser) { parser->pos = 0; parser->toknext = 0; - parser->cursize = NULL; + parser->toksuper = -1; } diff --git a/jsmn.h b/jsmn.h index 72040bc3b..91e285d25 100644 --- a/jsmn.h +++ b/jsmn.h @@ -44,9 +44,9 @@ typedef struct { * the string being parsed now and current position in that string */ typedef struct { - unsigned int pos; - int toknext; - int *cursize; + unsigned int pos; /* offset in the JSON string */ + int toknext; /* next token to allocate */ + int toksuper; /* suporior token node, e.g parent object or array */ } jsmn_parser; /** diff --git a/jsmn_test.c b/jsmn_test.c index a356a9527..0bbd82c2f 100644 --- a/jsmn_test.c +++ b/jsmn_test.c @@ -254,8 +254,9 @@ int test_array_nomem() { js = " [ 1, true, [123, \"hello\"]]"; for (i = 0; i < 6; i++) { - printf("i = %d\n", i); jsmn_init(&p); + memset(toksmall, 0, sizeof(toksmall)); + memset(toklarge, 0, sizeof(toklarge)); r = jsmn_parse(&p, js, toksmall, i); check(r == JSMN_ERROR_NOMEM); @@ -263,6 +264,9 @@ int test_array_nomem() { r = jsmn_parse(&p, js, toklarge, 10); check(r == JSMN_SUCCESS); + + check(toklarge[0].type == JSMN_ARRAY && toklarge[0].size == 3); + check(toklarge[3].type == JSMN_ARRAY && toklarge[3].size == 2); } return 0; } -- cgit v1.2.3 From af04595fe243554ee762f515c088f99ce26e2b27 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Thu, 2 Feb 2012 14:05:47 +0200 Subject: fix: error is returned if brackets are unmatched --- jsmn.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jsmn.c b/jsmn.c index e38a47252..cc300e360 100644 --- a/jsmn.c +++ b/jsmn.c @@ -161,6 +161,8 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, break; } } + /* Error if unmatched closing bracket */ + if (i == -1) return JSMN_ERROR_INVAL; for (; i >= 0; i--) { token = &tokens[i]; if (token->start != -1 && token->end == -1) { -- cgit v1.2.3 From e542dea54ef194c36d5815bfe18a1b7e0615d02b Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Mon, 3 Sep 2012 19:54:40 +0300 Subject: changed return value to jsmnerr_t, fixed warning about signed/unsigned values --- jsmn.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/jsmn.c b/jsmn.c index cc300e360..761a60468 100644 --- a/jsmn.c +++ b/jsmn.c @@ -31,7 +31,7 @@ static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, /** * Fills next available token with JSON primitive. */ -static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, +static jsmnerr_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, jsmntok_t *tokens, size_t num_tokens) { jsmntok_t *token; int start; @@ -72,7 +72,7 @@ found: /** * Filsl next token with JSON string. */ -static int jsmn_parse_string(jsmn_parser *parser, const char *js, +static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, jsmntok_t *tokens, size_t num_tokens) { jsmntok_t *token; @@ -123,13 +123,14 @@ static int jsmn_parse_string(jsmn_parser *parser, const char *js, */ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, unsigned int num_tokens) { - int r; + jsmnerr_t r; int i; + unsigned int tokindex; jsmntok_t *token; /* initialize the rest of tokens (they could be reallocated) */ - for (i = parser->toknext; i < num_tokens; i++) { - jsmn_fill_token(&tokens[i], JSMN_PRIMITIVE, -1, -1); + for (tokindex = parser->toknext; tokindex < num_tokens; tokindex++) { + jsmn_fill_token(&tokens[tokindex], JSMN_PRIMITIVE, -1, -1); } for (; js[parser->pos] != '\0'; parser->pos++) { -- cgit v1.2.3 From 974133db8531c7edd9969c8ebb04470318fc767d Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Sun, 23 Sep 2012 20:29:48 +0300 Subject: tokens array items are now being initialized during allocation, removed redundant code that significantly slowed down the parser --- jsmn.c | 19 +++++++------------ jsmn_test.c | 4 ++-- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/jsmn.c b/jsmn.c index 761a60468..9a96c4fc4 100644 --- a/jsmn.c +++ b/jsmn.c @@ -7,14 +7,14 @@ */ static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens, size_t num_tokens) { - unsigned int i; - for (i = parser->toknext; i < num_tokens; i++) { - if (tokens[i].start == -1 && tokens[i].end == -1) { - parser->toknext = i + 1; - return &tokens[i]; - } + jsmntok_t *tok; + if (parser->toknext >= num_tokens) { + return NULL; } - return NULL; + tok = &tokens[parser->toknext++]; + tok->start = tok->end = -1; + tok->size = 0; + return tok; } /** @@ -128,11 +128,6 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, unsigned int tokindex; jsmntok_t *token; - /* initialize the rest of tokens (they could be reallocated) */ - for (tokindex = parser->toknext; tokindex < num_tokens; tokindex++) { - jsmn_fill_token(&tokens[tokindex], JSMN_PRIMITIVE, -1, -1); - } - for (; js[parser->pos] != '\0'; parser->pos++) { char c; jsmntype_t type; diff --git a/jsmn_test.c b/jsmn_test.c index 0bbd82c2f..a33d6b88c 100644 --- a/jsmn_test.c +++ b/jsmn_test.c @@ -156,13 +156,13 @@ int test_partial_string() { r = jsmn_parse(&p, js, tok, 10); check(r == JSMN_ERROR_PART && tok[0].type == JSMN_STRING); check(TOKEN_STRING(js, tok[0], "x")); - check(TOKEN_EQ(tok[1], -1, -1, 0)); + check(p.toknext == 1); js = "\"x\": \"valu"; r = jsmn_parse(&p, js, tok, 10); check(r == JSMN_ERROR_PART && tok[0].type == JSMN_STRING); check(TOKEN_STRING(js, tok[0], "x")); - check(TOKEN_EQ(tok[1], -1, -1, 0)); + check(p.toknext == 1); js = "\"x\": \"value\""; r = jsmn_parse(&p, js, tok, 10); -- cgit v1.2.3 From cfbb5b509174552ed00ac0ad46d474d00cad6e9d Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Sun, 23 Sep 2012 20:30:56 +0300 Subject: removed unused variable --- jsmn.c | 1 - 1 file changed, 1 deletion(-) diff --git a/jsmn.c b/jsmn.c index 9a96c4fc4..ceb612219 100644 --- a/jsmn.c +++ b/jsmn.c @@ -125,7 +125,6 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, unsigned int num_tokens) { jsmnerr_t r; int i; - unsigned int tokindex; jsmntok_t *token; for (; js[parser->pos] != '\0'; parser->pos++) { -- cgit v1.2.3 From 37964b40ee94fdfdc9a8408a9ade9d7f3cb94683 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Wed, 26 Sep 2012 20:19:21 +0300 Subject: added optional parent token pointer support. Increases parsing speed --- jsmn.c | 33 ++++++++++++++++++++++++++++++++- jsmn.h | 3 +++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/jsmn.c b/jsmn.c index ceb612219..ef704f5e2 100644 --- a/jsmn.c +++ b/jsmn.c @@ -14,6 +14,9 @@ static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, tok = &tokens[parser->toknext++]; tok->start = tok->end = -1; tok->size = 0; +#ifdef JSMN_PARENT_LINKS + tok->parent = -1; +#endif return tok; } @@ -65,6 +68,9 @@ found: return JSMN_ERROR_NOMEM; } jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif parser->pos--; return JSMN_SUCCESS; } @@ -92,6 +98,9 @@ static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, return JSMN_ERROR_NOMEM; } jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif return JSMN_SUCCESS; } @@ -137,14 +146,35 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, token = jsmn_alloc_token(parser, tokens, num_tokens); if (token == NULL) return JSMN_ERROR_NOMEM; - if (parser->toksuper != -1) + if (parser->toksuper != -1) { tokens[parser->toksuper].size++; +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + } token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); token->start = parser->pos; parser->toksuper = parser->toknext - 1; break; case '}': case ']': type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); +#ifdef JSMN_PARENT_LINKS + if (parser->toknext < 1) { + return JSMN_ERROR_INVAL; + } + token = &tokens[parser->toknext - 1]; + for (;;) { + if (token->start != -1 && token->end == -1) { + token->end = parser->pos + 1; + parser->toksuper = token->parent; + break; + } + if (token->parent == -1) { + break; + } + token = &tokens[token->parent]; + } +#else for (i = parser->toknext - 1; i >= 0; i--) { token = &tokens[i]; if (token->start != -1 && token->end == -1) { @@ -165,6 +195,7 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, break; } } +#endif break; case '\"': r = jsmn_parse_string(parser, js, tokens, num_tokens); diff --git a/jsmn.h b/jsmn.h index 91e285d25..5914239ba 100644 --- a/jsmn.h +++ b/jsmn.h @@ -37,6 +37,9 @@ typedef struct { int start; int end; int size; +#ifdef JSMN_PARENT_LINKS + int parent; +#endif } jsmntok_t; /** -- cgit v1.2.3 From e0ceee893c86f7f727bd05aa0e96a716983b610a Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Wed, 26 Sep 2012 20:28:12 +0300 Subject: fixed strict mode: space symbols are allowed after primitives; added some new tests --- jsmn.c | 3 ++- jsmn_test.c | 12 ++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/jsmn.c b/jsmn.c index ef704f5e2..bbe5c063c 100644 --- a/jsmn.c +++ b/jsmn.c @@ -45,8 +45,9 @@ static jsmnerr_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, switch (js[parser->pos]) { #ifndef JSMN_STRICT /* In strict mode primitive must be followed by "," or "}" or "]" */ - case '\t' : case '\r' : case '\n' : case ' ' : case ':': + case ':': #endif + case '\t' : case '\r' : case '\n' : case ' ' : case ',' : case ']' : case '}' : goto found; } diff --git a/jsmn_test.c b/jsmn_test.c index a33d6b88c..257a7f0bb 100644 --- a/jsmn_test.c +++ b/jsmn_test.c @@ -60,6 +60,14 @@ int test_simple() { check(TOKEN_STRING(js, tokens[1], "a")); check(TOKEN_STRING(js, tokens[2], "0")); + js = "[\"a\":{},\"b\":{}]"; + r = jsmn_parse(&p, js, tokens, 10); + check(r == JSMN_SUCCESS); + + js = "{\n \"Day\": 26,\n \"Month\": 9,\n \"Year\": 12\n }"; + r = jsmn_parse(&p, js, tokens, 10); + check(r == JSMN_SUCCESS); + return 0; } @@ -68,7 +76,7 @@ int test_primitive() { jsmn_parser p; jsmntok_t tok[10]; const char *js; - +#ifndef JSMN_STRICT js = "\"boolVar\" : true"; jsmn_init(&p); r = jsmn_parse(&p, js, tok, 10); @@ -108,7 +116,7 @@ int test_primitive() { && tok[1].type == JSMN_PRIMITIVE); check(TOKEN_STRING(js, tok[0], "nullVar")); check(TOKEN_STRING(js, tok[1], "null")); - +#endif return 0; } -- cgit v1.2.3 From cf172e71d7d8802cccf0fb8369280c6e1aae89a2 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Mon, 1 Oct 2012 11:15:48 +0300 Subject: fixed tests: missing jsmn_init() before parsing; added tests for empty arrays/objects --- jsmn_test.c | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/jsmn_test.c b/jsmn_test.c index 257a7f0bb..4c9593f27 100644 --- a/jsmn_test.c +++ b/jsmn_test.c @@ -37,9 +37,46 @@ static void test(int (*func)(void), const char *name) { && strlen(s) == (t).end - (t).start) #define TOKEN_PRINT(t) \ - printf("start: %d, end: %d, type: %d\n", (t).start, (t).end, (t).type) + printf("start: %d, end: %d, type: %d, size: %d\n", \ + (t).start, (t).end, (t).type, (t).size) +int test_empty() { + const char *js; + int r; + jsmn_parser p; + jsmntok_t t[10]; + + js = "{}"; + jsmn_init(&p); + r = jsmn_parse(&p, js, t, 10); + check(r == JSMN_SUCCESS); + check(t[0].type == JSMN_OBJECT); + check(t[0].start == 0 && t[0].end == 2); + + js = "[]"; + jsmn_init(&p); + r = jsmn_parse(&p, js, t, 10); + check(r == JSMN_SUCCESS); + check(t[0].type == JSMN_ARRAY); + check(t[0].start == 0 && t[0].end == 2); + js = "{\"a\":[]}"; + jsmn_init(&p); + r = jsmn_parse(&p, js, t, 10); + check(r == JSMN_SUCCESS); + check(t[0].type == JSMN_OBJECT && t[0].start == 0 && t[0].end == 8); + check(t[1].type == JSMN_STRING && t[1].start == 2 && t[1].end == 3); + check(t[2].type == JSMN_ARRAY && t[2].start == 5 && t[2].end == 7); + + js = "[{},{}]"; + jsmn_init(&p); + r = jsmn_parse(&p, js, t, 10); + check(r == JSMN_SUCCESS); + check(t[0].type == JSMN_ARRAY && t[0].start == 0 && t[0].end == 7); + check(t[1].type == JSMN_OBJECT && t[1].start == 1 && t[1].end == 3); + check(t[2].type == JSMN_OBJECT && t[2].start == 4 && t[2].end == 6); + return 0; +} int test_simple() { const char *js; @@ -60,10 +97,12 @@ int test_simple() { check(TOKEN_STRING(js, tokens[1], "a")); check(TOKEN_STRING(js, tokens[2], "0")); + jsmn_init(&p); js = "[\"a\":{},\"b\":{}]"; r = jsmn_parse(&p, js, tokens, 10); check(r == JSMN_SUCCESS); + jsmn_init(&p); js = "{\n \"Day\": 26,\n \"Month\": 9,\n \"Year\": 12\n }"; r = jsmn_parse(&p, js, tokens, 10); check(r == JSMN_SUCCESS); @@ -280,6 +319,7 @@ int test_array_nomem() { } int main() { + test(test_empty, "general test for a empty JSON objects/arrays"); test(test_simple, "general test for a simple JSON string"); test(test_primitive, "test primitive JSON data types"); test(test_string, "test string JSON data types"); -- cgit v1.2.3 From 3d6fb06ee9636902380856ad3656cb0504b0fff3 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Mon, 1 Oct 2012 12:43:37 +0300 Subject: fixed typo --- jsmn.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsmn.h b/jsmn.h index 5914239ba..03b2c1aa4 100644 --- a/jsmn.h +++ b/jsmn.h @@ -49,7 +49,7 @@ typedef struct { typedef struct { unsigned int pos; /* offset in the JSON string */ int toknext; /* next token to allocate */ - int toksuper; /* suporior token node, e.g parent object or array */ + int toksuper; /* superior token node, e.g parent object or array */ } jsmn_parser; /** -- cgit v1.2.3 From 958c758f2e9eb501679ab3bd6cda7d14421e84f4 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Mon, 22 Oct 2012 21:54:46 +0300 Subject: fixed: invalid object/arrays were passed successfully in tree mode; added tests for this case --- jsmn.c | 3 +++ jsmn_test.c | 31 +++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/jsmn.c b/jsmn.c index bbe5c063c..1b918f532 100644 --- a/jsmn.c +++ b/jsmn.c @@ -166,6 +166,9 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, token = &tokens[parser->toknext - 1]; for (;;) { if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } token->end = parser->pos + 1; parser->toksuper = token->parent; break; diff --git a/jsmn_test.c b/jsmn_test.c index 4c9593f27..308faf65e 100644 --- a/jsmn_test.c +++ b/jsmn_test.c @@ -318,6 +318,36 @@ int test_array_nomem() { return 0; } +int test_objects_arrays() { + int i; + int r; + jsmn_parser p; + jsmntok_t tokens[10]; + const char *js; + + js = "[10}"; + jsmn_init(&p); + r = jsmn_parse(&p, js, tokens, 10); + check(r == JSMN_ERROR_INVAL); + + js = "[10]"; + jsmn_init(&p); + r = jsmn_parse(&p, js, tokens, 10); + check(r == JSMN_SUCCESS); + + js = "{\"a\": 1]"; + jsmn_init(&p); + r = jsmn_parse(&p, js, tokens, 10); + check(r == JSMN_ERROR_INVAL); + + js = "{\"a\": 1}"; + jsmn_init(&p); + r = jsmn_parse(&p, js, tokens, 10); + check(r == JSMN_SUCCESS); + + return 0; +} + int main() { test(test_empty, "general test for a empty JSON objects/arrays"); test(test_simple, "general test for a simple JSON string"); @@ -327,6 +357,7 @@ int main() { test(test_partial_array, "test partial array reading"); test(test_array_nomem, "test array reading with a smaller number of tokens"); test(test_unquoted_keys, "test unquoted keys (like in JavaScript)"); + test(test_objects_arrays, "test objects and arrays"); printf("\nPASSED: %d\nFAILED: %d\n", test_passed, test_failed); return 0; } -- cgit v1.2.3 From 6f4e2f7a565fb779159a299efe6e1e5110c2f1a6 Mon Sep 17 00:00:00 2001 From: del6597 Date: Wed, 17 Jul 2013 00:05:47 -0400 Subject: Added a method for jsmn_estimate_tokens(const char *json); --- jsmn.c | 22 ++++++++++++++++++++++ jsmn.h | 9 +++++++++ 2 files changed, 31 insertions(+) diff --git a/jsmn.c b/jsmn.c index 1b918f532..77117fc87 100644 --- a/jsmn.c +++ b/jsmn.c @@ -1,4 +1,5 @@ #include +#include #include "jsmn.h" @@ -31,6 +32,27 @@ static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, token->size = 0; } +/** + * Estimate the number of JSON tokens in the string given. + * It should return a number greater than or equal to the actual amount of items. + * If the JSON string is malformed an incorrect number will be returned. + * Of course if the malformed string is going to be parsed, then parsing + * will fail anyway. + */ +int jsmn_count_tokens(const char *json) { + int c = 1; + int i; + for(i=0;i Date: Fri, 19 Jul 2013 00:15:49 -0400 Subject: Adds checking to unicode characters that are \uXXXX where X is a hexidecimal digit Adds new tests for unicode character coverage --- jsmn.c | 14 +++++++++++++- jsmn_test.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/jsmn.c b/jsmn.c index 1b918f532..a9aa5fe4b 100644 --- a/jsmn.c +++ b/jsmn.c @@ -115,7 +115,19 @@ static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, break; /* Allows escaped symbol \uXXXX */ case 'u': - /* TODO */ + parser->pos++; + int i=0; + for(;i<4&&js[parser->pos] != '\0';i++) { + // If it isn't a hex character we have an error + if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || // 0-9 + (js[parser->pos] >= 65 && js[parser->pos] <= 70) || // A-F + (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { // a-f + parser->pos = start; + return JSMN_ERROR_INVAL; + } + parser->pos++; + } + parser->pos--; break; /* Unexpected symbol */ default: diff --git a/jsmn_test.c b/jsmn_test.c index 308faf65e..c381fcb87 100644 --- a/jsmn_test.c +++ b/jsmn_test.c @@ -348,6 +348,50 @@ int test_objects_arrays() { return 0; } +int test_unicode_characters() { + jsmn_parser p; + jsmntok_t tokens[10]; + const char *js; + + int r; + js = "{\"a\":\"\\uAbcD\"}"; + jsmn_init(&p); + r = jsmn_parse(&p, js, tokens, 10); + check(r == JSMN_SUCCESS); + + js = "{\"a\":\"str\\u0000\"}"; + jsmn_init(&p); + r = jsmn_parse(&p, js, tokens, 10); + check(r == JSMN_SUCCESS); + + js = "{\"a\":\"\\uFFFFstr\"}"; + jsmn_init(&p); + r = jsmn_parse(&p, js, tokens, 10); + check(r == JSMN_SUCCESS); + + js = "{\"a\":\"str\\uFFGFstr\"}"; + jsmn_init(&p); + r = jsmn_parse(&p, js, tokens, 10); + check(r == JSMN_ERROR_INVAL); + + js = "{\"a\":\"str\\u@FfF\"}"; + jsmn_init(&p); + r = jsmn_parse(&p, js, tokens, 10); + check(r == JSMN_ERROR_INVAL); + + js = "{\"a\":[\"\\u028\"]}"; + jsmn_init(&p); + r = jsmn_parse(&p, js, tokens, 10); + check(r == JSMN_ERROR_INVAL); + + js = "{\"a\":[\"\\u0280\"]}"; + jsmn_init(&p); + r = jsmn_parse(&p, js, tokens, 10); + check(r == JSMN_SUCCESS); + + return 0; +} + int main() { test(test_empty, "general test for a empty JSON objects/arrays"); test(test_simple, "general test for a simple JSON string"); @@ -358,6 +402,7 @@ int main() { test(test_array_nomem, "test array reading with a smaller number of tokens"); test(test_unquoted_keys, "test unquoted keys (like in JavaScript)"); test(test_objects_arrays, "test objects and arrays"); + test(test_unicode_characters, "test unicode characters"); printf("\nPASSED: %d\nFAILED: %d\n", test_passed, test_failed); return 0; } -- cgit v1.2.3 From ee10da0f1e97b12126596a8c436aacb8ad62c832 Mon Sep 17 00:00:00 2001 From: Peter Finlayson Date: Mon, 23 Dec 2013 12:29:33 +0200 Subject: delete jsmn_test.exe when doing make clean When building the jsmn test suite on Windows via mingw, the output executable is called "jsmn_test.exe" instead of just "jsmn_test". --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 0afdef4fd..ac947a3af 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,7 @@ jsmn_test.o: jsmn_test.c libjsmn.a clean: rm -f jsmn.o jsmn_test.o rm -f jsmn_test + rm -f jsmn_test.exe rm -f libjsmn.a .PHONY: all clean test -- cgit v1.2.3 From e397f0dfb652b39dd4d9f753d11035cf2bc0b7b2 Mon Sep 17 00:00:00 2001 From: Peter Finlayson Date: Mon, 23 Dec 2013 12:39:00 +0200 Subject: rename README so markdown renders in source control The readme file is in markdown, but most source control systems and repos (including bitbucket) expect a markdown readme to be called README.md --- README | 157 -------------------------------------------------------------- README.md | 157 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+), 157 deletions(-) delete mode 100644 README create mode 100644 README.md diff --git a/README b/README deleted file mode 100644 index c6e7686f0..000000000 --- a/README +++ /dev/null @@ -1,157 +0,0 @@ - -JSMN -==== - -jsmn (pronounced like 'jasmine') is a minimalistic JSON parser in C. It can be -easily integrated into resource-limited or embedded projects. - -You can find more information about JSON format at [json.org][1] - -Library sources are available at [bitbucket.org/zserge/jsmn][2] - -Philosophy ----------- - -Most JSON parsers offer you a bunch of functions to load JSON data, parse it -and extract any value by its name. jsmn proves that checking the correctness of -every JSON packet or allocating temporary objects to store parsed JSON fields -often is an overkill. - -JSON format itself is extremely simple, so why should we complicate it? - -jsmn is designed to be **robust** (it should work fine even with erroneous -data), **fast** (it should parse data on the fly), **portable** (no superfluous -dependencies or non-standard C extensions). An of course, **simplicity** is a -key feature - simple code style, simple algorithm, simple integration into -other projects. - -Features --------- - -* compatible with C89 -* no dependencies (even libc!) -* highly portable (tested on x86/amd64, ARM, AVR) -* about 200 lines of code -* extremely small code footprint -* API contains only 2 functions -* no dynamic memory allocation -* incremental single-pass parsing -* library code is covered with unit-tests - -Design ------- - -The rudimentary jsmn object is a **token**. Let's consider a JSON string: - - '{ "name" : "Jack", "age" : 27 }' - -It holds the following tokens: - -* Object: `{ "name" : "Jack", "age" : 27}` (the whole object) -* Strings: `"name"`, `"Jack"`, `"age"` (keys and some values) -* Number: `27` - -In jsmn, tokens do not hold any data, but point to token boundaries in JSON -string instead. In the example above jsmn will create tokens like: Object -[0..31], String [3..7], String [12..16], String [20..23], Number [27..29]. - -Every jsmn token has a type, which indicates the type of corresponding JSON -token. jsmn supports the following token types: - -* Object - a container of key-value pairs, e.g.: - `{ "foo":"bar", "x":0.3 }` -* Array - a sequence of values, e.g.: - `[ 1, 2, 3 ]` -* String - a quoted sequence of chars, e.g.: `"foo"` -* Primitive - a number, a boolean (`true`, `false`) or `null` - -Besides start/end positions, jsmn tokens for complex types (like arrays -or objects) also contain a number of child items, so you can easily follow -object hierarchy. - -This approach provides enough information for parsing any JSON data and makes -it possible to use zero-copy techniques. - -Install -------- - -To clone the repository you should have mercurial installed. Just run: - - $ hg clone http://bitbucket.org/zserge/jsmn jsmn - -Repository layout is simple: jsmn.c and jsmn.h are library files; demo.c is an -example of how to use jsmn (it is also used in unit tests); test.sh is a test -script. You will also find README, LICENSE and Makefile files inside. - -To build the library, run `make`. It is also recommended to run `make test`. -Let me know, if some tests fail. - -If build was successful, you should get a `libjsmn.a` library. -The header file you should include is called `"jsmn.h"`. - -API ---- - -Token types are described by `jsmntype_t`: - - typedef enum { - JSMN_OBJECT, - JSMN_ARRAY, - JSMN_STRING, - JSMN_PRIMITIVE - } jsmntype_t; - -**Note:** Unlike JSON data types, primitive tokens are not divided into -numbers, booleans and null, because one can easily tell the type using the -first character: - -* 't', 'f' - boolean -* 'n' - null -* '-', '0'..'9' - number - -Token is an object of `jsmntok_t` type: - - typedef struct { - jsmntype_t type; // Token type - int start; // Token start position - int end; // Token end position - int size; // Number of child (nested) tokens - } jsmntok_t; - -**Note:** string tokens point to the first character after -the opening quote and the previous symbol before final quote. This was made -to simplify string extraction from JSON data. - -All job is done by `jsmn_parser` object. You can initialize a new parser using: - - struct jsmn_parser parser; - jsmntok_t tokens[10]; - - // js - pointer to JSON string - // tokens - an array of tokens available - // 10 - number of tokens available - jsmn_init_parser(&parser, js, tokens, 10); - -This will create a parser, that can parse up to 10 JSON tokens from `js` string. - -Later, you can use `jsmn_parse(&parser)` function to process JSON string with the parser. -If something goes wrong, you will get an error. Error will be one of these: - -* `JSMN_SUCCESS` - everything went fine. String was parsed -* `JSMN_ERROR_INVAL` - bad token, JSON string is corrupted -* `JSMN_ERROR_NOMEM` - not enough tokens, JSON string is too large -* `JSMN_ERROR_PART` - JSON string is too short, expecting more JSON data - -If you get `JSON_ERROR_NOMEM`, you can re-allocate more tokens and call -`jsmn_parse` once more. If you read json data from the stream, you can -periodically call `jsmn_parse` and check if return value is `JSON_ERROR_PART`. -You will get this error until you reach the end of JSON data. - -Other info ----------- - -This software is distributed under [MIT license](http://www.opensource.org/licenses/mit-license.php), - so feel free to integrate it in your commercial products. - -[1]: http://www.json.org/ -[2]: https://bitbucket.org/zserge/jsmn/wiki/Home diff --git a/README.md b/README.md new file mode 100644 index 000000000..c6e7686f0 --- /dev/null +++ b/README.md @@ -0,0 +1,157 @@ + +JSMN +==== + +jsmn (pronounced like 'jasmine') is a minimalistic JSON parser in C. It can be +easily integrated into resource-limited or embedded projects. + +You can find more information about JSON format at [json.org][1] + +Library sources are available at [bitbucket.org/zserge/jsmn][2] + +Philosophy +---------- + +Most JSON parsers offer you a bunch of functions to load JSON data, parse it +and extract any value by its name. jsmn proves that checking the correctness of +every JSON packet or allocating temporary objects to store parsed JSON fields +often is an overkill. + +JSON format itself is extremely simple, so why should we complicate it? + +jsmn is designed to be **robust** (it should work fine even with erroneous +data), **fast** (it should parse data on the fly), **portable** (no superfluous +dependencies or non-standard C extensions). An of course, **simplicity** is a +key feature - simple code style, simple algorithm, simple integration into +other projects. + +Features +-------- + +* compatible with C89 +* no dependencies (even libc!) +* highly portable (tested on x86/amd64, ARM, AVR) +* about 200 lines of code +* extremely small code footprint +* API contains only 2 functions +* no dynamic memory allocation +* incremental single-pass parsing +* library code is covered with unit-tests + +Design +------ + +The rudimentary jsmn object is a **token**. Let's consider a JSON string: + + '{ "name" : "Jack", "age" : 27 }' + +It holds the following tokens: + +* Object: `{ "name" : "Jack", "age" : 27}` (the whole object) +* Strings: `"name"`, `"Jack"`, `"age"` (keys and some values) +* Number: `27` + +In jsmn, tokens do not hold any data, but point to token boundaries in JSON +string instead. In the example above jsmn will create tokens like: Object +[0..31], String [3..7], String [12..16], String [20..23], Number [27..29]. + +Every jsmn token has a type, which indicates the type of corresponding JSON +token. jsmn supports the following token types: + +* Object - a container of key-value pairs, e.g.: + `{ "foo":"bar", "x":0.3 }` +* Array - a sequence of values, e.g.: + `[ 1, 2, 3 ]` +* String - a quoted sequence of chars, e.g.: `"foo"` +* Primitive - a number, a boolean (`true`, `false`) or `null` + +Besides start/end positions, jsmn tokens for complex types (like arrays +or objects) also contain a number of child items, so you can easily follow +object hierarchy. + +This approach provides enough information for parsing any JSON data and makes +it possible to use zero-copy techniques. + +Install +------- + +To clone the repository you should have mercurial installed. Just run: + + $ hg clone http://bitbucket.org/zserge/jsmn jsmn + +Repository layout is simple: jsmn.c and jsmn.h are library files; demo.c is an +example of how to use jsmn (it is also used in unit tests); test.sh is a test +script. You will also find README, LICENSE and Makefile files inside. + +To build the library, run `make`. It is also recommended to run `make test`. +Let me know, if some tests fail. + +If build was successful, you should get a `libjsmn.a` library. +The header file you should include is called `"jsmn.h"`. + +API +--- + +Token types are described by `jsmntype_t`: + + typedef enum { + JSMN_OBJECT, + JSMN_ARRAY, + JSMN_STRING, + JSMN_PRIMITIVE + } jsmntype_t; + +**Note:** Unlike JSON data types, primitive tokens are not divided into +numbers, booleans and null, because one can easily tell the type using the +first character: + +* 't', 'f' - boolean +* 'n' - null +* '-', '0'..'9' - number + +Token is an object of `jsmntok_t` type: + + typedef struct { + jsmntype_t type; // Token type + int start; // Token start position + int end; // Token end position + int size; // Number of child (nested) tokens + } jsmntok_t; + +**Note:** string tokens point to the first character after +the opening quote and the previous symbol before final quote. This was made +to simplify string extraction from JSON data. + +All job is done by `jsmn_parser` object. You can initialize a new parser using: + + struct jsmn_parser parser; + jsmntok_t tokens[10]; + + // js - pointer to JSON string + // tokens - an array of tokens available + // 10 - number of tokens available + jsmn_init_parser(&parser, js, tokens, 10); + +This will create a parser, that can parse up to 10 JSON tokens from `js` string. + +Later, you can use `jsmn_parse(&parser)` function to process JSON string with the parser. +If something goes wrong, you will get an error. Error will be one of these: + +* `JSMN_SUCCESS` - everything went fine. String was parsed +* `JSMN_ERROR_INVAL` - bad token, JSON string is corrupted +* `JSMN_ERROR_NOMEM` - not enough tokens, JSON string is too large +* `JSMN_ERROR_PART` - JSON string is too short, expecting more JSON data + +If you get `JSON_ERROR_NOMEM`, you can re-allocate more tokens and call +`jsmn_parse` once more. If you read json data from the stream, you can +periodically call `jsmn_parse` and check if return value is `JSON_ERROR_PART`. +You will get this error until you reach the end of JSON data. + +Other info +---------- + +This software is distributed under [MIT license](http://www.opensource.org/licenses/mit-license.php), + so feel free to integrate it in your commercial products. + +[1]: http://www.json.org/ +[2]: https://bitbucket.org/zserge/jsmn/wiki/Home -- cgit v1.2.3 From 40392b73e3f3048e10f1338f7ab9b5e47a8aa08e Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Mon, 27 Jan 2014 15:58:43 +0200 Subject: fixed indentation and comments style after merge --- jsmn.c | 26 +++++++++---------- jsmn_test.c | 84 ++++++++++++++++++++++++++++++------------------------------- 2 files changed, 55 insertions(+), 55 deletions(-) diff --git a/jsmn.c b/jsmn.c index 1091b9509..4f70adb0d 100644 --- a/jsmn.c +++ b/jsmn.c @@ -116,19 +116,19 @@ static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, break; /* Allows escaped symbol \uXXXX */ case 'u': - parser->pos++; - int i=0; - for(;i<4&&js[parser->pos] != '\0';i++) { - // If it isn't a hex character we have an error - if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || // 0-9 - (js[parser->pos] >= 65 && js[parser->pos] <= 70) || // A-F - (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { // a-f - parser->pos = start; - return JSMN_ERROR_INVAL; - } - parser->pos++; - } - parser->pos--; + parser->pos++; + int i = 0; + for(; i < 4 && js[parser->pos] != '\0'; i++) { + /* If it isn't a hex character we have an error */ + if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ + (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ + (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ + parser->pos = start; + return JSMN_ERROR_INVAL; + } + parser->pos++; + } + parser->pos--; break; /* Unexpected symbol */ default: diff --git a/jsmn_test.c b/jsmn_test.c index c381fcb87..fe5f00045 100644 --- a/jsmn_test.c +++ b/jsmn_test.c @@ -349,47 +349,47 @@ int test_objects_arrays() { } int test_unicode_characters() { - jsmn_parser p; - jsmntok_t tokens[10]; - const char *js; - - int r; - js = "{\"a\":\"\\uAbcD\"}"; - jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 10); - check(r == JSMN_SUCCESS); - - js = "{\"a\":\"str\\u0000\"}"; - jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 10); - check(r == JSMN_SUCCESS); - - js = "{\"a\":\"\\uFFFFstr\"}"; - jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 10); - check(r == JSMN_SUCCESS); - - js = "{\"a\":\"str\\uFFGFstr\"}"; - jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 10); - check(r == JSMN_ERROR_INVAL); - - js = "{\"a\":\"str\\u@FfF\"}"; - jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 10); - check(r == JSMN_ERROR_INVAL); - - js = "{\"a\":[\"\\u028\"]}"; - jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 10); - check(r == JSMN_ERROR_INVAL); - - js = "{\"a\":[\"\\u0280\"]}"; - jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 10); - check(r == JSMN_SUCCESS); - - return 0; + jsmn_parser p; + jsmntok_t tokens[10]; + const char *js; + + int r; + js = "{\"a\":\"\\uAbcD\"}"; + jsmn_init(&p); + r = jsmn_parse(&p, js, tokens, 10); + check(r == JSMN_SUCCESS); + + js = "{\"a\":\"str\\u0000\"}"; + jsmn_init(&p); + r = jsmn_parse(&p, js, tokens, 10); + check(r == JSMN_SUCCESS); + + js = "{\"a\":\"\\uFFFFstr\"}"; + jsmn_init(&p); + r = jsmn_parse(&p, js, tokens, 10); + check(r == JSMN_SUCCESS); + + js = "{\"a\":\"str\\uFFGFstr\"}"; + jsmn_init(&p); + r = jsmn_parse(&p, js, tokens, 10); + check(r == JSMN_ERROR_INVAL); + + js = "{\"a\":\"str\\u@FfF\"}"; + jsmn_init(&p); + r = jsmn_parse(&p, js, tokens, 10); + check(r == JSMN_ERROR_INVAL); + + js = "{\"a\":[\"\\u028\"]}"; + jsmn_init(&p); + r = jsmn_parse(&p, js, tokens, 10); + check(r == JSMN_ERROR_INVAL); + + js = "{\"a\":[\"\\u0280\"]}"; + jsmn_init(&p); + r = jsmn_parse(&p, js, tokens, 10); + check(r == JSMN_SUCCESS); + + return 0; } int main() { @@ -402,7 +402,7 @@ int main() { test(test_array_nomem, "test array reading with a smaller number of tokens"); test(test_unquoted_keys, "test unquoted keys (like in JavaScript)"); test(test_objects_arrays, "test objects and arrays"); - test(test_unicode_characters, "test unicode characters"); + test(test_unicode_characters, "test unicode characters"); printf("\nPASSED: %d\nFAILED: %d\n", test_passed, test_failed); return 0; } -- cgit v1.2.3 From c29d151bfac8656c693fa5843279ff2273aa7381 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Mon, 27 Jan 2014 16:04:43 +0200 Subject: added link to the web page --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index c6e7686f0..db02fbc97 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,9 @@ You can find more information about JSON format at [json.org][1] Library sources are available at [bitbucket.org/zserge/jsmn][2] +The web page with some information about jsmn can be found at +[http://zserge.com/jsmn.html][3] + Philosophy ---------- @@ -155,3 +158,4 @@ This software is distributed under [MIT license](http://www.opensource.org/licen [1]: http://www.json.org/ [2]: https://bitbucket.org/zserge/jsmn/wiki/Home +[3]: http://zserge.com/jsmn.html -- cgit v1.2.3 From d0ca2df480f016936de58c03d59ab45ebc259f84 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Mon, 27 Jan 2014 17:11:54 +0200 Subject: fixed jsmntype_t definition --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index db02fbc97..abccffa43 100644 --- a/README.md +++ b/README.md @@ -98,10 +98,10 @@ API Token types are described by `jsmntype_t`: typedef enum { - JSMN_OBJECT, - JSMN_ARRAY, - JSMN_STRING, - JSMN_PRIMITIVE + JSMN_PRIMITIVE = 0, + JSMN_OBJECT = 1, + JSMN_ARRAY = 2, + JSMN_STRING = 3 } jsmntype_t; **Note:** Unlike JSON data types, primitive tokens are not divided into -- cgit v1.2.3 From da959780339c33ebe0c3d0f86351a9734d6eb08b Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Sun, 9 Feb 2014 17:04:39 +0200 Subject: added cast size_t to int to avoid warnings, removed string.h header since it was merged by accident --- jsmn.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jsmn.c b/jsmn.c index 4f70adb0d..a8e71ac96 100644 --- a/jsmn.c +++ b/jsmn.c @@ -1,5 +1,4 @@ #include -#include #include "jsmn.h" @@ -9,7 +8,7 @@ static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens, size_t num_tokens) { jsmntok_t *tok; - if (parser->toknext >= num_tokens) { + if (parser->toknext >= (int) num_tokens) { return NULL; } tok = &tokens[parser->toknext++]; -- cgit v1.2.3 From 86350f2bb1681c573abd44018ee49883989008dd Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Sun, 9 Feb 2014 17:09:15 +0200 Subject: a proper fix for signed comparison --- jsmn.c | 2 +- jsmn.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jsmn.c b/jsmn.c index a8e71ac96..2956230d3 100644 --- a/jsmn.c +++ b/jsmn.c @@ -8,7 +8,7 @@ static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens, size_t num_tokens) { jsmntok_t *tok; - if (parser->toknext >= (int) num_tokens) { + if (parser->toknext >= num_tokens) { return NULL; } tok = &tokens[parser->toknext++]; diff --git a/jsmn.h b/jsmn.h index 03b2c1aa4..5aa6e3cf1 100644 --- a/jsmn.h +++ b/jsmn.h @@ -48,7 +48,7 @@ typedef struct { */ typedef struct { unsigned int pos; /* offset in the JSON string */ - int toknext; /* next token to allocate */ + unsigned int toknext; /* next token to allocate */ int toksuper; /* superior token node, e.g parent object or array */ } jsmn_parser; -- cgit v1.2.3 From 385b42e740fd8e040057ba682e402a208f211db7 Mon Sep 17 00:00:00 2001 From: Gabriel Gritsch Date: Wed, 12 Feb 2014 17:53:45 +0100 Subject: - Added possibility to specify the lenght of the input buffer (if not null-terminated). - Added "extern C" to use it with CPP. --- jsmn.c | 10 ++++++++-- jsmn.h | 10 +++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/jsmn.c b/jsmn.c index 2956230d3..0bf92ff83 100644 --- a/jsmn.c +++ b/jsmn.c @@ -1,4 +1,5 @@ #include +#include #include "jsmn.h" @@ -143,13 +144,18 @@ static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, /** * Parse JSON string and fill tokens. */ -jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, +jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, int js_length, jsmntok_t *tokens, unsigned int num_tokens) { jsmnerr_t r; int i; jsmntok_t *token; + + if (js_length <= 0) + { + js_length = INT_MAX; + } - for (; js[parser->pos] != '\0'; parser->pos++) { + for (; parser->pos < js_length && js[parser->pos] != '\0'; parser->pos++) { char c; jsmntype_t type; diff --git a/jsmn.h b/jsmn.h index 5aa6e3cf1..ecb4752c6 100644 --- a/jsmn.h +++ b/jsmn.h @@ -1,6 +1,10 @@ #ifndef __JSMN_H_ #define __JSMN_H_ +#ifdef __cplusplus +extern "C" { +#endif + /** * JSON type identifier. Basic types are: * o Object @@ -61,7 +65,11 @@ void jsmn_init(jsmn_parser *parser); * Run JSON parser. It parses a JSON data string into and array of tokens, each describing * a single JSON object. */ -jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, +jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, int js_length, jsmntok_t *tokens, unsigned int num_tokens); +#ifdef __cplusplus +} +#endif + #endif /* __JSMN_H_ */ -- cgit v1.2.3 From 659842c65c58724673fcc8a3ff7bd9408e5804f9 Mon Sep 17 00:00:00 2001 From: Gabriel Gritsch Date: Sun, 16 Feb 2014 10:42:27 +0100 Subject: - Added possibility to specify the lenght of the input buffer (if not null-terminated). - Added "extern C" to use it with CPP. --- jsmn.c | 20 +++++++++++++------- jsmn.h | 11 +++++++++-- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/jsmn.c b/jsmn.c index 0bf92ff83..729d5a2f1 100644 --- a/jsmn.c +++ b/jsmn.c @@ -1,5 +1,6 @@ #include -#include +#include // for SIZE_MAX +//#include // for UINT_MAX #include "jsmn.h" @@ -141,19 +142,24 @@ static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, return JSMN_ERROR_PART; } +static inline jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, unsigned int num_tokens) +{ + return jsmn_parseV2(parser, js, SIZE_MAX /*UINT_MAX*/, tokens, num_tokens); +} + /** * Parse JSON string and fill tokens. */ -jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, int js_length, jsmntok_t *tokens, +jsmnerr_t jsmn_parseV2(jsmn_parser *parser, const char *js, size_t js_length, jsmntok_t *tokens, unsigned int num_tokens) { + if (js==NULL || js_length==0) + { + return JSMN_ERROR_PART; + } + jsmnerr_t r; int i; jsmntok_t *token; - - if (js_length <= 0) - { - js_length = INT_MAX; - } for (; parser->pos < js_length && js[parser->pos] != '\0'; parser->pos++) { char c; diff --git a/jsmn.h b/jsmn.h index ecb4752c6..8f10cf9df 100644 --- a/jsmn.h +++ b/jsmn.h @@ -62,10 +62,17 @@ typedef struct { void jsmn_init(jsmn_parser *parser); /** - * Run JSON parser. It parses a JSON data string into and array of tokens, each describing + * Run JSON parser. It parses a JSON data null-terminated-string into and array of tokens, each describing * a single JSON object. */ -jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, int js_length, +static inline jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, + jsmntok_t *tokens, unsigned int num_tokens); + +/** + * Run JSON parser. It parses a JSON data buffer with specified length into and array of tokens, each describing + * a single JSON object. + */ +jsmnerr_t jsmn_parseV2(jsmn_parser *parser, const char *js, size_t js_length, jsmntok_t *tokens, unsigned int num_tokens); #ifdef __cplusplus -- cgit v1.2.3 From c91adcedede3392e910bd5befbe4ec24ad136db7 Mon Sep 17 00:00:00 2001 From: Gabriel Gritsch Date: Sun, 16 Feb 2014 10:45:20 +0100 Subject: - Added possibility to specify the lenght of the input buffer (if not null-terminated). - Added "extern C" to use it with CPP. --- jsmn.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsmn.c b/jsmn.c index 729d5a2f1..9ab2007bf 100644 --- a/jsmn.c +++ b/jsmn.c @@ -1,6 +1,6 @@ #include -#include // for SIZE_MAX //#include // for UINT_MAX +#include // for SIZE_MAX #include "jsmn.h" -- cgit v1.2.3 From 733b8e958e64196b5f440fe5d631e45c7445a094 Mon Sep 17 00:00:00 2001 From: Gabriel Gritsch Date: Sun, 16 Feb 2014 10:54:19 +0100 Subject: Changes for SIZE_MAX --- jsmn.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/jsmn.c b/jsmn.c index 9ab2007bf..b54b9411a 100644 --- a/jsmn.c +++ b/jsmn.c @@ -1,7 +1,11 @@ #include -//#include // for UINT_MAX +//#include #include // for SIZE_MAX +#ifndef SIZE_MAX // SIZE_MAX came in C99 + #define SIZE_MAX ((size_t)-1) +#endif + #include "jsmn.h" /** @@ -144,7 +148,7 @@ static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, static inline jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, unsigned int num_tokens) { - return jsmn_parseV2(parser, js, SIZE_MAX /*UINT_MAX*/, tokens, num_tokens); + return jsmn_parseV2(parser, js, SIZE_MAX, tokens, num_tokens); } /** -- cgit v1.2.3 From 809c7c6db1fd8691db78900b952f94150e7d98c9 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Thu, 20 Feb 2014 22:27:07 +0200 Subject: added way to estimate number of tokens before parsing --- jsmn.c | 27 +++++++++--- jsmn.h | 2 - jsmn_test.c | 139 ++++++++++++++++++++++++++++++++++++++++++++++++------------ 3 files changed, 132 insertions(+), 36 deletions(-) diff --git a/jsmn.c b/jsmn.c index 2956230d3..e0644f57d 100644 --- a/jsmn.c +++ b/jsmn.c @@ -63,6 +63,10 @@ static jsmnerr_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, #endif found: + if (tokens == NULL) { + parser->pos--; + return 0; + } token = jsmn_alloc_token(parser, tokens, num_tokens); if (token == NULL) { parser->pos = start; @@ -73,7 +77,7 @@ found: token->parent = parser->toksuper; #endif parser->pos--; - return JSMN_SUCCESS; + return 0; } /** @@ -93,6 +97,9 @@ static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, /* Quote: end of string */ if (c == '\"') { + if (tokens == NULL) { + return 0; + } token = jsmn_alloc_token(parser, tokens, num_tokens); if (token == NULL) { parser->pos = start; @@ -102,7 +109,7 @@ static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, #ifdef JSMN_PARENT_LINKS token->parent = parser->toksuper; #endif - return JSMN_SUCCESS; + return 0; } /* Backslash: Quoted symbol expected */ @@ -148,6 +155,7 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, jsmnerr_t r; int i; jsmntok_t *token; + int count = 0; for (; js[parser->pos] != '\0'; parser->pos++) { char c; @@ -156,6 +164,10 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, c = js[parser->pos]; switch (c) { case '{': case '[': + if (tokens == NULL) { + count++; + break; + } token = jsmn_alloc_token(parser, tokens, num_tokens); if (token == NULL) return JSMN_ERROR_NOMEM; @@ -170,6 +182,8 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, parser->toksuper = parser->toknext - 1; break; case '}': case ']': + if (tokens == NULL) + break; type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); #ifdef JSMN_PARENT_LINKS if (parser->toknext < 1) { @@ -216,7 +230,8 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, case '\"': r = jsmn_parse_string(parser, js, tokens, num_tokens); if (r < 0) return r; - if (parser->toksuper != -1) + count++; + if (parser->toksuper != -1 && tokens != NULL) tokens[parser->toksuper].size++; break; case '\t' : case '\r' : case '\n' : case ':' : case ',': case ' ': @@ -232,7 +247,8 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, #endif r = jsmn_parse_primitive(parser, js, tokens, num_tokens); if (r < 0) return r; - if (parser->toksuper != -1) + count++; + if (parser->toksuper != -1 && tokens != NULL) tokens[parser->toksuper].size++; break; @@ -241,7 +257,6 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, default: return JSMN_ERROR_INVAL; #endif - } } @@ -252,7 +267,7 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, } } - return JSMN_SUCCESS; + return count; } /** diff --git a/jsmn.h b/jsmn.h index 5aa6e3cf1..ee66d4c2b 100644 --- a/jsmn.h +++ b/jsmn.h @@ -22,8 +22,6 @@ typedef enum { JSMN_ERROR_INVAL = -2, /* The string is not a full JSON packet, more bytes expected */ JSMN_ERROR_PART = -3, - /* Everything was fine */ - JSMN_SUCCESS = 0 } jsmnerr_t; /** diff --git a/jsmn_test.c b/jsmn_test.c index fe5f00045..fd1e8524a 100644 --- a/jsmn_test.c +++ b/jsmn_test.c @@ -2,8 +2,6 @@ #include #include -#include "jsmn.c" - static int test_passed = 0; static int test_failed = 0; @@ -40,6 +38,8 @@ static void test(int (*func)(void), const char *name) { printf("start: %d, end: %d, type: %d, size: %d\n", \ (t).start, (t).end, (t).type, (t).size) +#include "jsmn.c" + int test_empty() { const char *js; int r; @@ -49,21 +49,21 @@ int test_empty() { js = "{}"; jsmn_init(&p); r = jsmn_parse(&p, js, t, 10); - check(r == JSMN_SUCCESS); + check(r >= 0); check(t[0].type == JSMN_OBJECT); check(t[0].start == 0 && t[0].end == 2); js = "[]"; jsmn_init(&p); r = jsmn_parse(&p, js, t, 10); - check(r == JSMN_SUCCESS); + check(r >= 0); check(t[0].type == JSMN_ARRAY); check(t[0].start == 0 && t[0].end == 2); js = "{\"a\":[]}"; jsmn_init(&p); r = jsmn_parse(&p, js, t, 10); - check(r == JSMN_SUCCESS); + check(r >= 0); check(t[0].type == JSMN_OBJECT && t[0].start == 0 && t[0].end == 8); check(t[1].type == JSMN_STRING && t[1].start == 2 && t[1].end == 3); check(t[2].type == JSMN_ARRAY && t[2].start == 5 && t[2].end == 7); @@ -71,7 +71,7 @@ int test_empty() { js = "[{},{}]"; jsmn_init(&p); r = jsmn_parse(&p, js, t, 10); - check(r == JSMN_SUCCESS); + check(r >= 0); check(t[0].type == JSMN_ARRAY && t[0].start == 0 && t[0].end == 7); check(t[1].type == JSMN_OBJECT && t[1].start == 1 && t[1].end == 3); check(t[2].type == JSMN_OBJECT && t[2].start == 4 && t[2].end == 6); @@ -88,7 +88,7 @@ int test_simple() { jsmn_init(&p); r = jsmn_parse(&p, js, tokens, 10); - check(r == JSMN_SUCCESS); + check(r >= 0); check(TOKEN_EQ(tokens[0], 0, 8, JSMN_OBJECT)); check(TOKEN_EQ(tokens[1], 2, 3, JSMN_STRING)); check(TOKEN_EQ(tokens[2], 6, 7, JSMN_PRIMITIVE)); @@ -100,12 +100,12 @@ int test_simple() { jsmn_init(&p); js = "[\"a\":{},\"b\":{}]"; r = jsmn_parse(&p, js, tokens, 10); - check(r == JSMN_SUCCESS); + check(r >= 0); jsmn_init(&p); js = "{\n \"Day\": 26,\n \"Month\": 9,\n \"Year\": 12\n }"; r = jsmn_parse(&p, js, tokens, 10); - check(r == JSMN_SUCCESS); + check(r >= 0); return 0; } @@ -119,7 +119,7 @@ int test_primitive() { js = "\"boolVar\" : true"; jsmn_init(&p); r = jsmn_parse(&p, js, tok, 10); - check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING + check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_PRIMITIVE); check(TOKEN_STRING(js, tok[0], "boolVar")); check(TOKEN_STRING(js, tok[1], "true")); @@ -127,7 +127,7 @@ int test_primitive() { js = "\"boolVar\" : false"; jsmn_init(&p); r = jsmn_parse(&p, js, tok, 10); - check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING + check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_PRIMITIVE); check(TOKEN_STRING(js, tok[0], "boolVar")); check(TOKEN_STRING(js, tok[1], "false")); @@ -135,7 +135,7 @@ int test_primitive() { js = "\"intVar\" : 12345"; jsmn_init(&p); r = jsmn_parse(&p, js, tok, 10); - check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING + check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_PRIMITIVE); check(TOKEN_STRING(js, tok[0], "intVar")); check(TOKEN_STRING(js, tok[1], "12345")); @@ -143,7 +143,7 @@ int test_primitive() { js = "\"floatVar\" : 12.345"; jsmn_init(&p); r = jsmn_parse(&p, js, tok, 10); - check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING + check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_PRIMITIVE); check(TOKEN_STRING(js, tok[0], "floatVar")); check(TOKEN_STRING(js, tok[1], "12.345")); @@ -151,7 +151,7 @@ int test_primitive() { js = "\"nullVar\" : null"; jsmn_init(&p); r = jsmn_parse(&p, js, tok, 10); - check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING + check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_PRIMITIVE); check(TOKEN_STRING(js, tok[0], "nullVar")); check(TOKEN_STRING(js, tok[1], "null")); @@ -168,7 +168,7 @@ int test_string() { js = "\"strVar\" : \"hello world\""; jsmn_init(&p); r = jsmn_parse(&p, js, tok, 10); - check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING + check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_STRING); check(TOKEN_STRING(js, tok[0], "strVar")); check(TOKEN_STRING(js, tok[1], "hello world")); @@ -176,7 +176,7 @@ int test_string() { js = "\"strVar\" : \"escapes: \\/\\r\\n\\t\\b\\f\\\"\\\\\""; jsmn_init(&p); r = jsmn_parse(&p, js, tok, 10); - check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING + check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_STRING); check(TOKEN_STRING(js, tok[0], "strVar")); check(TOKEN_STRING(js, tok[1], "escapes: \\/\\r\\n\\t\\b\\f\\\"\\\\")); @@ -184,7 +184,7 @@ int test_string() { js = "\"strVar\" : \"\""; jsmn_init(&p); r = jsmn_parse(&p, js, tok, 10); - check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING + check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_STRING); check(TOKEN_STRING(js, tok[0], "strVar")); check(TOKEN_STRING(js, tok[1], "")); @@ -213,14 +213,14 @@ int test_partial_string() { js = "\"x\": \"value\""; r = jsmn_parse(&p, js, tok, 10); - check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING + check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_STRING); check(TOKEN_STRING(js, tok[0], "x")); check(TOKEN_STRING(js, tok[1], "value")); js = "\"x\": \"value\", \"y\": \"value y\""; r = jsmn_parse(&p, js, tok, 10); - check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING + check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_STRING && tok[2].type == JSMN_STRING && tok[3].type == JSMN_STRING); check(TOKEN_STRING(js, tok[0], "x")); @@ -242,7 +242,7 @@ int test_unquoted_keys() { js = "key1: \"value\"\nkey2 : 123"; r = jsmn_parse(&p, js, tok, 10); - check(r == JSMN_SUCCESS && tok[0].type == JSMN_PRIMITIVE + check(r >= 0 && tok[0].type == JSMN_PRIMITIVE && tok[1].type == JSMN_STRING && tok[2].type == JSMN_PRIMITIVE && tok[3].type == JSMN_PRIMITIVE); check(TOKEN_STRING(js, tok[0], "key1")); @@ -282,7 +282,7 @@ int test_partial_array() { js = " [ 1, true, [123, \"hello\"]]"; r = jsmn_parse(&p, js, tok, 10); - check(r == JSMN_SUCCESS && tok[0].type == JSMN_ARRAY + check(r >= 0 && tok[0].type == JSMN_ARRAY && tok[1].type == JSMN_PRIMITIVE && tok[2].type == JSMN_PRIMITIVE && tok[3].type == JSMN_ARRAY && tok[4].type == JSMN_PRIMITIVE && tok[5].type == JSMN_STRING); @@ -310,7 +310,7 @@ int test_array_nomem() { memcpy(toklarge, toksmall, sizeof(toksmall)); r = jsmn_parse(&p, js, toklarge, 10); - check(r == JSMN_SUCCESS); + check(r >= 0); check(toklarge[0].type == JSMN_ARRAY && toklarge[0].size == 3); check(toklarge[3].type == JSMN_ARRAY && toklarge[3].size == 2); @@ -333,7 +333,7 @@ int test_objects_arrays() { js = "[10]"; jsmn_init(&p); r = jsmn_parse(&p, js, tokens, 10); - check(r == JSMN_SUCCESS); + check(r >= 0); js = "{\"a\": 1]"; jsmn_init(&p); @@ -343,11 +343,45 @@ int test_objects_arrays() { js = "{\"a\": 1}"; jsmn_init(&p); r = jsmn_parse(&p, js, tokens, 10); - check(r == JSMN_SUCCESS); + check(r >= 0); return 0; } +int test_issue_22() { + int i; + int r; + jsmn_parser p; + jsmntok_t tokens[128]; + const char *js; + + js = "{ \"height\":10, \"layers\":[ { \"data\":[6,6], \"height\":10, " + "\"name\":\"Calque de Tile 1\", \"opacity\":1, \"type\":\"tilelayer\", " + "\"visible\":true, \"width\":10, \"x\":0, \"y\":0 }], " + "\"orientation\":\"orthogonal\", \"properties\": { }, \"tileheight\":32, " + "\"tilesets\":[ { \"firstgid\":1, \"image\":\"..\\/images\\/tiles.png\", " + "\"imageheight\":64, \"imagewidth\":160, \"margin\":0, \"name\":\"Tiles\", " + "\"properties\":{}, \"spacing\":0, \"tileheight\":32, \"tilewidth\":32 }], " + "\"tilewidth\":32, \"version\":1, \"width\":10 }"; + jsmn_init(&p); + r = jsmn_parse(&p, js, tokens, 128); + check(r >= 0); +#if 0 + for (i = 1; tokens[i].end < tokens[0].end; i++) { + if (tokens[i].type == JSMN_STRING || tokens[i].type == JSMN_PRIMITIVE) { + printf("%.*s\n", tokens[i].end - tokens[i].start, js + tokens[i].start); + } else if (tokens[i].type == JSMN_ARRAY) { + printf("[%d elems]\n", tokens[i].size); + } else if (tokens[i].type == JSMN_OBJECT) { + printf("{%d elems}\n", tokens[i].size); + } else { + TOKEN_PRINT(tokens[i]); + } + } +#endif + return 0; +} + int test_unicode_characters() { jsmn_parser p; jsmntok_t tokens[10]; @@ -357,17 +391,17 @@ int test_unicode_characters() { js = "{\"a\":\"\\uAbcD\"}"; jsmn_init(&p); r = jsmn_parse(&p, js, tokens, 10); - check(r == JSMN_SUCCESS); + check(r >= 0); js = "{\"a\":\"str\\u0000\"}"; jsmn_init(&p); r = jsmn_parse(&p, js, tokens, 10); - check(r == JSMN_SUCCESS); + check(r >= 0); js = "{\"a\":\"\\uFFFFstr\"}"; jsmn_init(&p); r = jsmn_parse(&p, js, tokens, 10); - check(r == JSMN_SUCCESS); + check(r >= 0); js = "{\"a\":\"str\\uFFGFstr\"}"; jsmn_init(&p); @@ -387,8 +421,55 @@ int test_unicode_characters() { js = "{\"a\":[\"\\u0280\"]}"; jsmn_init(&p); r = jsmn_parse(&p, js, tokens, 10); - check(r == JSMN_SUCCESS); + check(r >= 0); + + return 0; +} + +int test_count() { + jsmn_parser p; + const char *js; + js = "{}"; + jsmn_init(&p); + check(jsmn_parse(&p, js, NULL, 0) == 1); + + js = "[]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, NULL, 0) == 1); + + js = "[[]]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, NULL, 0) == 2); + + js = "[[], []]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, NULL, 0) == 3); + + js = "[[], []]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, NULL, 0) == 3); + + js = "[[], [[]], [[], []]]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, NULL, 0) == 7); + + js = "[\"a\", [[], []]]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, NULL, 0) == 5); + + js = "[[], \"[], [[]]\", [[]]]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, NULL, 0) == 5); + + js = "[1, 2, 3]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, NULL, 0) == 4); + + js = "[1, 2, [3, \"a\"], null]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, NULL, 0) == 7); + return 0; } @@ -403,6 +484,8 @@ int main() { test(test_unquoted_keys, "test unquoted keys (like in JavaScript)"); test(test_objects_arrays, "test objects and arrays"); test(test_unicode_characters, "test unicode characters"); + test(test_issue_22, "test issue #22"); + test(test_count, "test tokens count estimation"); printf("\nPASSED: %d\nFAILED: %d\n", test_passed, test_failed); return 0; } -- cgit v1.2.3 From f0ae25f873ca13cf798a7381d9ea4c5d51db3a79 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Fri, 21 Feb 2014 00:11:52 +0200 Subject: added way to specify json string length, added test for non-strict mode --- jsmn.c | 20 ++++---- jsmn.h | 2 +- jsmn_test.c | 160 ++++++++++++++++++++++++++++++++++++++++++------------------ 3 files changed, 123 insertions(+), 59 deletions(-) diff --git a/jsmn.c b/jsmn.c index e0644f57d..563813965 100644 --- a/jsmn.c +++ b/jsmn.c @@ -35,13 +35,13 @@ static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, * Fills next available token with JSON primitive. */ static jsmnerr_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, - jsmntok_t *tokens, size_t num_tokens) { + size_t len, jsmntok_t *tokens, size_t num_tokens) { jsmntok_t *token; int start; start = parser->pos; - for (; js[parser->pos] != '\0'; parser->pos++) { + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { switch (js[parser->pos]) { #ifndef JSMN_STRICT /* In strict mode primitive must be followed by "," or "}" or "]" */ @@ -84,7 +84,7 @@ found: * Filsl next token with JSON string. */ static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, - jsmntok_t *tokens, size_t num_tokens) { + size_t len, jsmntok_t *tokens, size_t num_tokens) { jsmntok_t *token; int start = parser->pos; @@ -92,7 +92,7 @@ static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, parser->pos++; /* Skip starting quote */ - for (; js[parser->pos] != '\0'; parser->pos++) { + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { char c = js[parser->pos]; /* Quote: end of string */ @@ -150,22 +150,22 @@ static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, /** * Parse JSON string and fill tokens. */ -jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, - unsigned int num_tokens) { +jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t len, + jsmntok_t *tokens, unsigned int num_tokens) { jsmnerr_t r; int i; jsmntok_t *token; int count = 0; - for (; js[parser->pos] != '\0'; parser->pos++) { + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { char c; jsmntype_t type; c = js[parser->pos]; switch (c) { case '{': case '[': + count++; if (tokens == NULL) { - count++; break; } token = jsmn_alloc_token(parser, tokens, num_tokens); @@ -228,7 +228,7 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, #endif break; case '\"': - r = jsmn_parse_string(parser, js, tokens, num_tokens); + r = jsmn_parse_string(parser, js, len, tokens, num_tokens); if (r < 0) return r; count++; if (parser->toksuper != -1 && tokens != NULL) @@ -245,7 +245,7 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, /* In non-strict mode every unquoted value is a primitive */ default: #endif - r = jsmn_parse_primitive(parser, js, tokens, num_tokens); + r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); if (r < 0) return r; count++; if (parser->toksuper != -1 && tokens != NULL) diff --git a/jsmn.h b/jsmn.h index ee66d4c2b..54930ad55 100644 --- a/jsmn.h +++ b/jsmn.h @@ -59,7 +59,7 @@ void jsmn_init(jsmn_parser *parser); * Run JSON parser. It parses a JSON data string into and array of tokens, each describing * a single JSON object. */ -jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, +jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, unsigned int num_tokens); #endif /* __JSMN_H_ */ diff --git a/jsmn_test.c b/jsmn_test.c index fd1e8524a..c5bfc0354 100644 --- a/jsmn_test.c +++ b/jsmn_test.c @@ -38,6 +38,7 @@ static void test(int (*func)(void), const char *name) { printf("start: %d, end: %d, type: %d, size: %d\n", \ (t).start, (t).end, (t).type, (t).size) +#define JSMN_STRICT #include "jsmn.c" int test_empty() { @@ -48,21 +49,21 @@ int test_empty() { js = "{}"; jsmn_init(&p); - r = jsmn_parse(&p, js, t, 10); + r = jsmn_parse(&p, js, strlen(js), t, 10); check(r >= 0); check(t[0].type == JSMN_OBJECT); check(t[0].start == 0 && t[0].end == 2); js = "[]"; jsmn_init(&p); - r = jsmn_parse(&p, js, t, 10); + r = jsmn_parse(&p, js, strlen(js), t, 10); check(r >= 0); check(t[0].type == JSMN_ARRAY); check(t[0].start == 0 && t[0].end == 2); js = "{\"a\":[]}"; jsmn_init(&p); - r = jsmn_parse(&p, js, t, 10); + r = jsmn_parse(&p, js, strlen(js), t, 10); check(r >= 0); check(t[0].type == JSMN_OBJECT && t[0].start == 0 && t[0].end == 8); check(t[1].type == JSMN_STRING && t[1].start == 2 && t[1].end == 3); @@ -70,7 +71,7 @@ int test_empty() { js = "[{},{}]"; jsmn_init(&p); - r = jsmn_parse(&p, js, t, 10); + r = jsmn_parse(&p, js, strlen(js), t, 10); check(r >= 0); check(t[0].type == JSMN_ARRAY && t[0].start == 0 && t[0].end == 7); check(t[1].type == JSMN_OBJECT && t[1].start == 1 && t[1].end == 3); @@ -87,7 +88,7 @@ int test_simple() { js = "{\"a\": 0}"; jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 10); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); check(r >= 0); check(TOKEN_EQ(tokens[0], 0, 8, JSMN_OBJECT)); check(TOKEN_EQ(tokens[1], 2, 3, JSMN_STRING)); @@ -99,12 +100,12 @@ int test_simple() { jsmn_init(&p); js = "[\"a\":{},\"b\":{}]"; - r = jsmn_parse(&p, js, tokens, 10); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); check(r >= 0); jsmn_init(&p); js = "{\n \"Day\": 26,\n \"Month\": 9,\n \"Year\": 12\n }"; - r = jsmn_parse(&p, js, tokens, 10); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); check(r >= 0); return 0; @@ -118,7 +119,7 @@ int test_primitive() { #ifndef JSMN_STRICT js = "\"boolVar\" : true"; jsmn_init(&p); - r = jsmn_parse(&p, js, tok, 10); + r = jsmn_parse(&p, js, strlen(js), tok, 10); check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_PRIMITIVE); check(TOKEN_STRING(js, tok[0], "boolVar")); @@ -126,7 +127,7 @@ int test_primitive() { js = "\"boolVar\" : false"; jsmn_init(&p); - r = jsmn_parse(&p, js, tok, 10); + r = jsmn_parse(&p, js, strlen(js), tok, 10); check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_PRIMITIVE); check(TOKEN_STRING(js, tok[0], "boolVar")); @@ -134,7 +135,7 @@ int test_primitive() { js = "\"intVar\" : 12345"; jsmn_init(&p); - r = jsmn_parse(&p, js, tok, 10); + r = jsmn_parse(&p, js, strlen(js), tok, 10); check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_PRIMITIVE); check(TOKEN_STRING(js, tok[0], "intVar")); @@ -142,7 +143,7 @@ int test_primitive() { js = "\"floatVar\" : 12.345"; jsmn_init(&p); - r = jsmn_parse(&p, js, tok, 10); + r = jsmn_parse(&p, js, strlen(js), tok, 10); check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_PRIMITIVE); check(TOKEN_STRING(js, tok[0], "floatVar")); @@ -150,7 +151,7 @@ int test_primitive() { js = "\"nullVar\" : null"; jsmn_init(&p); - r = jsmn_parse(&p, js, tok, 10); + r = jsmn_parse(&p, js, strlen(js), tok, 10); check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_PRIMITIVE); check(TOKEN_STRING(js, tok[0], "nullVar")); @@ -167,7 +168,7 @@ int test_string() { js = "\"strVar\" : \"hello world\""; jsmn_init(&p); - r = jsmn_parse(&p, js, tok, 10); + r = jsmn_parse(&p, js, strlen(js), tok, 10); check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_STRING); check(TOKEN_STRING(js, tok[0], "strVar")); @@ -175,7 +176,7 @@ int test_string() { js = "\"strVar\" : \"escapes: \\/\\r\\n\\t\\b\\f\\\"\\\\\""; jsmn_init(&p); - r = jsmn_parse(&p, js, tok, 10); + r = jsmn_parse(&p, js, strlen(js), tok, 10); check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_STRING); check(TOKEN_STRING(js, tok[0], "strVar")); @@ -183,7 +184,7 @@ int test_string() { js = "\"strVar\" : \"\""; jsmn_init(&p); - r = jsmn_parse(&p, js, tok, 10); + r = jsmn_parse(&p, js, strlen(js), tok, 10); check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_STRING); check(TOKEN_STRING(js, tok[0], "strVar")); @@ -200,26 +201,26 @@ int test_partial_string() { jsmn_init(&p); js = "\"x\": \"va"; - r = jsmn_parse(&p, js, tok, 10); + r = jsmn_parse(&p, js, strlen(js), tok, 10); check(r == JSMN_ERROR_PART && tok[0].type == JSMN_STRING); check(TOKEN_STRING(js, tok[0], "x")); check(p.toknext == 1); js = "\"x\": \"valu"; - r = jsmn_parse(&p, js, tok, 10); + r = jsmn_parse(&p, js, strlen(js), tok, 10); check(r == JSMN_ERROR_PART && tok[0].type == JSMN_STRING); check(TOKEN_STRING(js, tok[0], "x")); check(p.toknext == 1); js = "\"x\": \"value\""; - r = jsmn_parse(&p, js, tok, 10); + r = jsmn_parse(&p, js, strlen(js), tok, 10); check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_STRING); check(TOKEN_STRING(js, tok[0], "x")); check(TOKEN_STRING(js, tok[1], "value")); js = "\"x\": \"value\", \"y\": \"value y\""; - r = jsmn_parse(&p, js, tok, 10); + r = jsmn_parse(&p, js, strlen(js), tok, 10); check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_STRING && tok[2].type == JSMN_STRING && tok[3].type == JSMN_STRING); @@ -241,7 +242,7 @@ int test_unquoted_keys() { jsmn_init(&p); js = "key1: \"value\"\nkey2 : 123"; - r = jsmn_parse(&p, js, tok, 10); + r = jsmn_parse(&p, js, strlen(js), tok, 10); check(r >= 0 && tok[0].type == JSMN_PRIMITIVE && tok[1].type == JSMN_STRING && tok[2].type == JSMN_PRIMITIVE && tok[3].type == JSMN_PRIMITIVE); @@ -261,18 +262,18 @@ int test_partial_array() { jsmn_init(&p); js = " [ 1, true, "; - r = jsmn_parse(&p, js, tok, 10); + r = jsmn_parse(&p, js, strlen(js), tok, 10); check(r == JSMN_ERROR_PART && tok[0].type == JSMN_ARRAY && tok[1].type == JSMN_PRIMITIVE && tok[2].type == JSMN_PRIMITIVE); js = " [ 1, true, [123, \"hello"; - r = jsmn_parse(&p, js, tok, 10); + r = jsmn_parse(&p, js, strlen(js), tok, 10); check(r == JSMN_ERROR_PART && tok[0].type == JSMN_ARRAY && tok[1].type == JSMN_PRIMITIVE && tok[2].type == JSMN_PRIMITIVE && tok[3].type == JSMN_ARRAY && tok[4].type == JSMN_PRIMITIVE); js = " [ 1, true, [123, \"hello\"]"; - r = jsmn_parse(&p, js, tok, 10); + r = jsmn_parse(&p, js, strlen(js), tok, 10); check(r == JSMN_ERROR_PART && tok[0].type == JSMN_ARRAY && tok[1].type == JSMN_PRIMITIVE && tok[2].type == JSMN_PRIMITIVE && tok[3].type == JSMN_ARRAY && tok[4].type == JSMN_PRIMITIVE @@ -281,7 +282,7 @@ int test_partial_array() { check(tok[3].size == 2); js = " [ 1, true, [123, \"hello\"]]"; - r = jsmn_parse(&p, js, tok, 10); + r = jsmn_parse(&p, js, strlen(js), tok, 10); check(r >= 0 && tok[0].type == JSMN_ARRAY && tok[1].type == JSMN_PRIMITIVE && tok[2].type == JSMN_PRIMITIVE && tok[3].type == JSMN_ARRAY && tok[4].type == JSMN_PRIMITIVE @@ -304,12 +305,12 @@ int test_array_nomem() { jsmn_init(&p); memset(toksmall, 0, sizeof(toksmall)); memset(toklarge, 0, sizeof(toklarge)); - r = jsmn_parse(&p, js, toksmall, i); + r = jsmn_parse(&p, js, strlen(js), toksmall, i); check(r == JSMN_ERROR_NOMEM); memcpy(toklarge, toksmall, sizeof(toksmall)); - r = jsmn_parse(&p, js, toklarge, 10); + r = jsmn_parse(&p, js, strlen(js), toklarge, 10); check(r >= 0); check(toklarge[0].type == JSMN_ARRAY && toklarge[0].size == 3); @@ -327,22 +328,22 @@ int test_objects_arrays() { js = "[10}"; jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 10); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); check(r == JSMN_ERROR_INVAL); js = "[10]"; jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 10); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); check(r >= 0); js = "{\"a\": 1]"; jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 10); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); check(r == JSMN_ERROR_INVAL); js = "{\"a\": 1}"; jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 10); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); check(r >= 0); return 0; @@ -364,7 +365,7 @@ int test_issue_22() { "\"properties\":{}, \"spacing\":0, \"tileheight\":32, \"tilewidth\":32 }], " "\"tilewidth\":32, \"version\":1, \"width\":10 }"; jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 128); + r = jsmn_parse(&p, js, strlen(js), tokens, 128); check(r >= 0); #if 0 for (i = 1; tokens[i].end < tokens[0].end; i++) { @@ -390,89 +391,150 @@ int test_unicode_characters() { int r; js = "{\"a\":\"\\uAbcD\"}"; jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 10); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); check(r >= 0); js = "{\"a\":\"str\\u0000\"}"; jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 10); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); check(r >= 0); js = "{\"a\":\"\\uFFFFstr\"}"; jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 10); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); check(r >= 0); js = "{\"a\":\"str\\uFFGFstr\"}"; jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 10); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); check(r == JSMN_ERROR_INVAL); js = "{\"a\":\"str\\u@FfF\"}"; jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 10); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); check(r == JSMN_ERROR_INVAL); js = "{\"a\":[\"\\u028\"]}"; jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 10); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); check(r == JSMN_ERROR_INVAL); js = "{\"a\":[\"\\u0280\"]}"; jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 10); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); check(r >= 0); return 0; } +int test_input_length() { + const char *js; + int r; + jsmn_parser p; + jsmntok_t tokens[10]; + + js = "{\"a\": 0}garbage"; + + jsmn_init(&p); + r = jsmn_parse(&p, js, 8, tokens, 10); + check(r == 3); + check(TOKEN_STRING(js, tokens[0], "{\"a\": 0}")); + check(TOKEN_STRING(js, tokens[1], "a")); + check(TOKEN_STRING(js, tokens[2], "0")); + + return 0; +} + int test_count() { jsmn_parser p; const char *js; js = "{}"; jsmn_init(&p); - check(jsmn_parse(&p, js, NULL, 0) == 1); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 1); js = "[]"; jsmn_init(&p); - check(jsmn_parse(&p, js, NULL, 0) == 1); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 1); js = "[[]]"; jsmn_init(&p); - check(jsmn_parse(&p, js, NULL, 0) == 2); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 2); js = "[[], []]"; jsmn_init(&p); - check(jsmn_parse(&p, js, NULL, 0) == 3); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 3); js = "[[], []]"; jsmn_init(&p); - check(jsmn_parse(&p, js, NULL, 0) == 3); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 3); js = "[[], [[]], [[], []]]"; jsmn_init(&p); - check(jsmn_parse(&p, js, NULL, 0) == 7); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 7); js = "[\"a\", [[], []]]"; jsmn_init(&p); - check(jsmn_parse(&p, js, NULL, 0) == 5); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 5); js = "[[], \"[], [[]]\", [[]]]"; jsmn_init(&p); - check(jsmn_parse(&p, js, NULL, 0) == 5); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 5); js = "[1, 2, 3]"; jsmn_init(&p); - check(jsmn_parse(&p, js, NULL, 0) == 4); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 4); js = "[1, 2, [3, \"a\"], null]"; jsmn_init(&p); - check(jsmn_parse(&p, js, NULL, 0) == 7); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 7); return 0; } +/** A huge redefinition of everything to include jsmn in non-script mode */ +#define jsmn_init jsmn_init_nonstrict +#define jsmn_parse jsmn_parse_nonstrict +#define jsmn_parser jsmn_parser_nonstrict +#define jsmn_alloc_token jsmn_alloc_token_nonstrict +#define jsmn_fill_token jsmn_fill_token_nonstrict +#define jsmn_parse_primitive jsmn_parse_primitive_nonstrict +#define jsmn_parse_string jsmn_parse_string_nonstrict +#define jsmntype_t jsmntype_nonstrict_t +#define jsmnerr_t jsmnerr_nonstrict_t +#define jsmntok_t jsmntok_nonstrict_t +#define JSMN_PRIMITIVE JSMN_PRIMITIVE_NONSTRICT +#define JSMN_OBJECT JSMN_OBJECT_NONSTRICT +#define JSMN_ARRAY JSMN_ARRAY_NONSTRICT +#define JSMN_STRING JSMN_STRING_NONSTRICT +#define JSMN_ERROR_NOMEM JSMN_ERROR_NOMEM_NONSTRICT +#define JSMN_ERROR_INVAL JSMN_ERROR_INVAL_NONSTRICT +#define JSMN_ERROR_PART JSMN_ERROR_PART_NONSTRICT +#undef __JSMN_H_ +#undef JSMN_STRICT +#include "jsmn.c" + +int test_nonstrict() { + const char *js; + int r; + jsmn_parser p; + jsmntok_t tokens[10]; + + js = "a: 0garbage"; + + jsmn_init(&p); + r = jsmn_parse(&p, js, 4, tokens, 10); + check(r == 2); + check(TOKEN_STRING(js, tokens[0], "a")); + check(TOKEN_STRING(js, tokens[1], "0")); + + js = "Day : 26\nMonth : Sep\n\nYear: 12"; + jsmn_init(&p); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); + check(r == 6); + return 0; +} + int main() { test(test_empty, "general test for a empty JSON objects/arrays"); test(test_simple, "general test for a simple JSON string"); @@ -484,8 +546,10 @@ int main() { test(test_unquoted_keys, "test unquoted keys (like in JavaScript)"); test(test_objects_arrays, "test objects and arrays"); test(test_unicode_characters, "test unicode characters"); + test(test_input_length, "test strings that are not null-terminated"); test(test_issue_22, "test issue #22"); test(test_count, "test tokens count estimation"); + test(test_nonstrict, "test for non-strict mode"); printf("\nPASSED: %d\nFAILED: %d\n", test_passed, test_failed); return 0; } -- cgit v1.2.3 From 5faee057cb4f75fcb72a57e628cf697bf0467c85 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Fri, 21 Feb 2014 00:24:29 +0200 Subject: edited readme about what's inside the repo. closes issue #19 --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index abccffa43..b33113c17 100644 --- a/README.md +++ b/README.md @@ -82,9 +82,8 @@ To clone the repository you should have mercurial installed. Just run: $ hg clone http://bitbucket.org/zserge/jsmn jsmn -Repository layout is simple: jsmn.c and jsmn.h are library files; demo.c is an -example of how to use jsmn (it is also used in unit tests); test.sh is a test -script. You will also find README, LICENSE and Makefile files inside. +Repository layout is simple: jsmn.c and jsmn.h are library files, tests are in +the jsmn\_test.c, you will also find README, LICENSE and Makefile files inside. To build the library, run `make`. It is also recommended to run `make test`. Let me know, if some tests fail. -- cgit v1.2.3 From a89501b2fbc4fa30b0653328931cf7d9162851e6 Mon Sep 17 00:00:00 2001 From: Gabriel Gritsch Date: Fri, 21 Feb 2014 09:52:23 +0100 Subject: Integrate changes made by Serge Zaitsev. --- README.md | 5 +- jsmn.c | 61 +++++++------ jsmn.h | 21 +---- jsmn_test.c | 277 ++++++++++++++++++++++++++++++++++++++++++++++-------------- 4 files changed, 246 insertions(+), 118 deletions(-) diff --git a/README.md b/README.md index abccffa43..b33113c17 100644 --- a/README.md +++ b/README.md @@ -82,9 +82,8 @@ To clone the repository you should have mercurial installed. Just run: $ hg clone http://bitbucket.org/zserge/jsmn jsmn -Repository layout is simple: jsmn.c and jsmn.h are library files; demo.c is an -example of how to use jsmn (it is also used in unit tests); test.sh is a test -script. You will also find README, LICENSE and Makefile files inside. +Repository layout is simple: jsmn.c and jsmn.h are library files, tests are in +the jsmn\_test.c, you will also find README, LICENSE and Makefile files inside. To build the library, run `make`. It is also recommended to run `make test`. Let me know, if some tests fail. diff --git a/jsmn.c b/jsmn.c index b54b9411a..563813965 100644 --- a/jsmn.c +++ b/jsmn.c @@ -1,10 +1,4 @@ #include -//#include -#include // for SIZE_MAX - -#ifndef SIZE_MAX // SIZE_MAX came in C99 - #define SIZE_MAX ((size_t)-1) -#endif #include "jsmn.h" @@ -41,13 +35,13 @@ static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, * Fills next available token with JSON primitive. */ static jsmnerr_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, - jsmntok_t *tokens, size_t num_tokens) { + size_t len, jsmntok_t *tokens, size_t num_tokens) { jsmntok_t *token; int start; start = parser->pos; - for (; js[parser->pos] != '\0'; parser->pos++) { + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { switch (js[parser->pos]) { #ifndef JSMN_STRICT /* In strict mode primitive must be followed by "," or "}" or "]" */ @@ -69,6 +63,10 @@ static jsmnerr_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, #endif found: + if (tokens == NULL) { + parser->pos--; + return 0; + } token = jsmn_alloc_token(parser, tokens, num_tokens); if (token == NULL) { parser->pos = start; @@ -79,14 +77,14 @@ found: token->parent = parser->toksuper; #endif parser->pos--; - return JSMN_SUCCESS; + return 0; } /** * Filsl next token with JSON string. */ static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, - jsmntok_t *tokens, size_t num_tokens) { + size_t len, jsmntok_t *tokens, size_t num_tokens) { jsmntok_t *token; int start = parser->pos; @@ -94,11 +92,14 @@ static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, parser->pos++; /* Skip starting quote */ - for (; js[parser->pos] != '\0'; parser->pos++) { + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { char c = js[parser->pos]; /* Quote: end of string */ if (c == '\"') { + if (tokens == NULL) { + return 0; + } token = jsmn_alloc_token(parser, tokens, num_tokens); if (token == NULL) { parser->pos = start; @@ -108,7 +109,7 @@ static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, #ifdef JSMN_PARENT_LINKS token->parent = parser->toksuper; #endif - return JSMN_SUCCESS; + return 0; } /* Backslash: Quoted symbol expected */ @@ -146,32 +147,27 @@ static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, return JSMN_ERROR_PART; } -static inline jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, jsmntok_t *tokens, unsigned int num_tokens) -{ - return jsmn_parseV2(parser, js, SIZE_MAX, tokens, num_tokens); -} - /** * Parse JSON string and fill tokens. */ -jsmnerr_t jsmn_parseV2(jsmn_parser *parser, const char *js, size_t js_length, jsmntok_t *tokens, - unsigned int num_tokens) { - if (js==NULL || js_length==0) - { - return JSMN_ERROR_PART; - } - +jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t len, + jsmntok_t *tokens, unsigned int num_tokens) { jsmnerr_t r; int i; jsmntok_t *token; + int count = 0; - for (; parser->pos < js_length && js[parser->pos] != '\0'; parser->pos++) { + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { char c; jsmntype_t type; c = js[parser->pos]; switch (c) { case '{': case '[': + count++; + if (tokens == NULL) { + break; + } token = jsmn_alloc_token(parser, tokens, num_tokens); if (token == NULL) return JSMN_ERROR_NOMEM; @@ -186,6 +182,8 @@ jsmnerr_t jsmn_parseV2(jsmn_parser *parser, const char *js, size_t js_length, js parser->toksuper = parser->toknext - 1; break; case '}': case ']': + if (tokens == NULL) + break; type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); #ifdef JSMN_PARENT_LINKS if (parser->toknext < 1) { @@ -230,9 +228,10 @@ jsmnerr_t jsmn_parseV2(jsmn_parser *parser, const char *js, size_t js_length, js #endif break; case '\"': - r = jsmn_parse_string(parser, js, tokens, num_tokens); + r = jsmn_parse_string(parser, js, len, tokens, num_tokens); if (r < 0) return r; - if (parser->toksuper != -1) + count++; + if (parser->toksuper != -1 && tokens != NULL) tokens[parser->toksuper].size++; break; case '\t' : case '\r' : case '\n' : case ':' : case ',': case ' ': @@ -246,9 +245,10 @@ jsmnerr_t jsmn_parseV2(jsmn_parser *parser, const char *js, size_t js_length, js /* In non-strict mode every unquoted value is a primitive */ default: #endif - r = jsmn_parse_primitive(parser, js, tokens, num_tokens); + r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); if (r < 0) return r; - if (parser->toksuper != -1) + count++; + if (parser->toksuper != -1 && tokens != NULL) tokens[parser->toksuper].size++; break; @@ -257,7 +257,6 @@ jsmnerr_t jsmn_parseV2(jsmn_parser *parser, const char *js, size_t js_length, js default: return JSMN_ERROR_INVAL; #endif - } } @@ -268,7 +267,7 @@ jsmnerr_t jsmn_parseV2(jsmn_parser *parser, const char *js, size_t js_length, js } } - return JSMN_SUCCESS; + return count; } /** diff --git a/jsmn.h b/jsmn.h index 8f10cf9df..54930ad55 100644 --- a/jsmn.h +++ b/jsmn.h @@ -1,10 +1,6 @@ #ifndef __JSMN_H_ #define __JSMN_H_ -#ifdef __cplusplus -extern "C" { -#endif - /** * JSON type identifier. Basic types are: * o Object @@ -26,8 +22,6 @@ typedef enum { JSMN_ERROR_INVAL = -2, /* The string is not a full JSON packet, more bytes expected */ JSMN_ERROR_PART = -3, - /* Everything was fine */ - JSMN_SUCCESS = 0 } jsmnerr_t; /** @@ -62,21 +56,10 @@ typedef struct { void jsmn_init(jsmn_parser *parser); /** - * Run JSON parser. It parses a JSON data null-terminated-string into and array of tokens, each describing + * Run JSON parser. It parses a JSON data string into and array of tokens, each describing * a single JSON object. */ -static inline jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, +jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, unsigned int num_tokens); -/** - * Run JSON parser. It parses a JSON data buffer with specified length into and array of tokens, each describing - * a single JSON object. - */ -jsmnerr_t jsmn_parseV2(jsmn_parser *parser, const char *js, size_t js_length, - jsmntok_t *tokens, unsigned int num_tokens); - -#ifdef __cplusplus -} -#endif - #endif /* __JSMN_H_ */ diff --git a/jsmn_test.c b/jsmn_test.c index fe5f00045..c5bfc0354 100644 --- a/jsmn_test.c +++ b/jsmn_test.c @@ -2,8 +2,6 @@ #include #include -#include "jsmn.c" - static int test_passed = 0; static int test_failed = 0; @@ -40,6 +38,9 @@ static void test(int (*func)(void), const char *name) { printf("start: %d, end: %d, type: %d, size: %d\n", \ (t).start, (t).end, (t).type, (t).size) +#define JSMN_STRICT +#include "jsmn.c" + int test_empty() { const char *js; int r; @@ -48,30 +49,30 @@ int test_empty() { js = "{}"; jsmn_init(&p); - r = jsmn_parse(&p, js, t, 10); - check(r == JSMN_SUCCESS); + r = jsmn_parse(&p, js, strlen(js), t, 10); + check(r >= 0); check(t[0].type == JSMN_OBJECT); check(t[0].start == 0 && t[0].end == 2); js = "[]"; jsmn_init(&p); - r = jsmn_parse(&p, js, t, 10); - check(r == JSMN_SUCCESS); + r = jsmn_parse(&p, js, strlen(js), t, 10); + check(r >= 0); check(t[0].type == JSMN_ARRAY); check(t[0].start == 0 && t[0].end == 2); js = "{\"a\":[]}"; jsmn_init(&p); - r = jsmn_parse(&p, js, t, 10); - check(r == JSMN_SUCCESS); + r = jsmn_parse(&p, js, strlen(js), t, 10); + check(r >= 0); check(t[0].type == JSMN_OBJECT && t[0].start == 0 && t[0].end == 8); check(t[1].type == JSMN_STRING && t[1].start == 2 && t[1].end == 3); check(t[2].type == JSMN_ARRAY && t[2].start == 5 && t[2].end == 7); js = "[{},{}]"; jsmn_init(&p); - r = jsmn_parse(&p, js, t, 10); - check(r == JSMN_SUCCESS); + r = jsmn_parse(&p, js, strlen(js), t, 10); + check(r >= 0); check(t[0].type == JSMN_ARRAY && t[0].start == 0 && t[0].end == 7); check(t[1].type == JSMN_OBJECT && t[1].start == 1 && t[1].end == 3); check(t[2].type == JSMN_OBJECT && t[2].start == 4 && t[2].end == 6); @@ -87,8 +88,8 @@ int test_simple() { js = "{\"a\": 0}"; jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 10); - check(r == JSMN_SUCCESS); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); + check(r >= 0); check(TOKEN_EQ(tokens[0], 0, 8, JSMN_OBJECT)); check(TOKEN_EQ(tokens[1], 2, 3, JSMN_STRING)); check(TOKEN_EQ(tokens[2], 6, 7, JSMN_PRIMITIVE)); @@ -99,13 +100,13 @@ int test_simple() { jsmn_init(&p); js = "[\"a\":{},\"b\":{}]"; - r = jsmn_parse(&p, js, tokens, 10); - check(r == JSMN_SUCCESS); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); + check(r >= 0); jsmn_init(&p); js = "{\n \"Day\": 26,\n \"Month\": 9,\n \"Year\": 12\n }"; - r = jsmn_parse(&p, js, tokens, 10); - check(r == JSMN_SUCCESS); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); + check(r >= 0); return 0; } @@ -118,40 +119,40 @@ int test_primitive() { #ifndef JSMN_STRICT js = "\"boolVar\" : true"; jsmn_init(&p); - r = jsmn_parse(&p, js, tok, 10); - check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING + r = jsmn_parse(&p, js, strlen(js), tok, 10); + check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_PRIMITIVE); check(TOKEN_STRING(js, tok[0], "boolVar")); check(TOKEN_STRING(js, tok[1], "true")); js = "\"boolVar\" : false"; jsmn_init(&p); - r = jsmn_parse(&p, js, tok, 10); - check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING + r = jsmn_parse(&p, js, strlen(js), tok, 10); + check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_PRIMITIVE); check(TOKEN_STRING(js, tok[0], "boolVar")); check(TOKEN_STRING(js, tok[1], "false")); js = "\"intVar\" : 12345"; jsmn_init(&p); - r = jsmn_parse(&p, js, tok, 10); - check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING + r = jsmn_parse(&p, js, strlen(js), tok, 10); + check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_PRIMITIVE); check(TOKEN_STRING(js, tok[0], "intVar")); check(TOKEN_STRING(js, tok[1], "12345")); js = "\"floatVar\" : 12.345"; jsmn_init(&p); - r = jsmn_parse(&p, js, tok, 10); - check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING + r = jsmn_parse(&p, js, strlen(js), tok, 10); + check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_PRIMITIVE); check(TOKEN_STRING(js, tok[0], "floatVar")); check(TOKEN_STRING(js, tok[1], "12.345")); js = "\"nullVar\" : null"; jsmn_init(&p); - r = jsmn_parse(&p, js, tok, 10); - check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING + r = jsmn_parse(&p, js, strlen(js), tok, 10); + check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_PRIMITIVE); check(TOKEN_STRING(js, tok[0], "nullVar")); check(TOKEN_STRING(js, tok[1], "null")); @@ -167,24 +168,24 @@ int test_string() { js = "\"strVar\" : \"hello world\""; jsmn_init(&p); - r = jsmn_parse(&p, js, tok, 10); - check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING + r = jsmn_parse(&p, js, strlen(js), tok, 10); + check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_STRING); check(TOKEN_STRING(js, tok[0], "strVar")); check(TOKEN_STRING(js, tok[1], "hello world")); js = "\"strVar\" : \"escapes: \\/\\r\\n\\t\\b\\f\\\"\\\\\""; jsmn_init(&p); - r = jsmn_parse(&p, js, tok, 10); - check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING + r = jsmn_parse(&p, js, strlen(js), tok, 10); + check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_STRING); check(TOKEN_STRING(js, tok[0], "strVar")); check(TOKEN_STRING(js, tok[1], "escapes: \\/\\r\\n\\t\\b\\f\\\"\\\\")); js = "\"strVar\" : \"\""; jsmn_init(&p); - r = jsmn_parse(&p, js, tok, 10); - check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING + r = jsmn_parse(&p, js, strlen(js), tok, 10); + check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_STRING); check(TOKEN_STRING(js, tok[0], "strVar")); check(TOKEN_STRING(js, tok[1], "")); @@ -200,27 +201,27 @@ int test_partial_string() { jsmn_init(&p); js = "\"x\": \"va"; - r = jsmn_parse(&p, js, tok, 10); + r = jsmn_parse(&p, js, strlen(js), tok, 10); check(r == JSMN_ERROR_PART && tok[0].type == JSMN_STRING); check(TOKEN_STRING(js, tok[0], "x")); check(p.toknext == 1); js = "\"x\": \"valu"; - r = jsmn_parse(&p, js, tok, 10); + r = jsmn_parse(&p, js, strlen(js), tok, 10); check(r == JSMN_ERROR_PART && tok[0].type == JSMN_STRING); check(TOKEN_STRING(js, tok[0], "x")); check(p.toknext == 1); js = "\"x\": \"value\""; - r = jsmn_parse(&p, js, tok, 10); - check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING + r = jsmn_parse(&p, js, strlen(js), tok, 10); + check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_STRING); check(TOKEN_STRING(js, tok[0], "x")); check(TOKEN_STRING(js, tok[1], "value")); js = "\"x\": \"value\", \"y\": \"value y\""; - r = jsmn_parse(&p, js, tok, 10); - check(r == JSMN_SUCCESS && tok[0].type == JSMN_STRING + r = jsmn_parse(&p, js, strlen(js), tok, 10); + check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_STRING && tok[2].type == JSMN_STRING && tok[3].type == JSMN_STRING); check(TOKEN_STRING(js, tok[0], "x")); @@ -241,8 +242,8 @@ int test_unquoted_keys() { jsmn_init(&p); js = "key1: \"value\"\nkey2 : 123"; - r = jsmn_parse(&p, js, tok, 10); - check(r == JSMN_SUCCESS && tok[0].type == JSMN_PRIMITIVE + r = jsmn_parse(&p, js, strlen(js), tok, 10); + check(r >= 0 && tok[0].type == JSMN_PRIMITIVE && tok[1].type == JSMN_STRING && tok[2].type == JSMN_PRIMITIVE && tok[3].type == JSMN_PRIMITIVE); check(TOKEN_STRING(js, tok[0], "key1")); @@ -261,18 +262,18 @@ int test_partial_array() { jsmn_init(&p); js = " [ 1, true, "; - r = jsmn_parse(&p, js, tok, 10); + r = jsmn_parse(&p, js, strlen(js), tok, 10); check(r == JSMN_ERROR_PART && tok[0].type == JSMN_ARRAY && tok[1].type == JSMN_PRIMITIVE && tok[2].type == JSMN_PRIMITIVE); js = " [ 1, true, [123, \"hello"; - r = jsmn_parse(&p, js, tok, 10); + r = jsmn_parse(&p, js, strlen(js), tok, 10); check(r == JSMN_ERROR_PART && tok[0].type == JSMN_ARRAY && tok[1].type == JSMN_PRIMITIVE && tok[2].type == JSMN_PRIMITIVE && tok[3].type == JSMN_ARRAY && tok[4].type == JSMN_PRIMITIVE); js = " [ 1, true, [123, \"hello\"]"; - r = jsmn_parse(&p, js, tok, 10); + r = jsmn_parse(&p, js, strlen(js), tok, 10); check(r == JSMN_ERROR_PART && tok[0].type == JSMN_ARRAY && tok[1].type == JSMN_PRIMITIVE && tok[2].type == JSMN_PRIMITIVE && tok[3].type == JSMN_ARRAY && tok[4].type == JSMN_PRIMITIVE @@ -281,8 +282,8 @@ int test_partial_array() { check(tok[3].size == 2); js = " [ 1, true, [123, \"hello\"]]"; - r = jsmn_parse(&p, js, tok, 10); - check(r == JSMN_SUCCESS && tok[0].type == JSMN_ARRAY + r = jsmn_parse(&p, js, strlen(js), tok, 10); + check(r >= 0 && tok[0].type == JSMN_ARRAY && tok[1].type == JSMN_PRIMITIVE && tok[2].type == JSMN_PRIMITIVE && tok[3].type == JSMN_ARRAY && tok[4].type == JSMN_PRIMITIVE && tok[5].type == JSMN_STRING); @@ -304,13 +305,13 @@ int test_array_nomem() { jsmn_init(&p); memset(toksmall, 0, sizeof(toksmall)); memset(toklarge, 0, sizeof(toklarge)); - r = jsmn_parse(&p, js, toksmall, i); + r = jsmn_parse(&p, js, strlen(js), toksmall, i); check(r == JSMN_ERROR_NOMEM); memcpy(toklarge, toksmall, sizeof(toksmall)); - r = jsmn_parse(&p, js, toklarge, 10); - check(r == JSMN_SUCCESS); + r = jsmn_parse(&p, js, strlen(js), toklarge, 10); + check(r >= 0); check(toklarge[0].type == JSMN_ARRAY && toklarge[0].size == 3); check(toklarge[3].type == JSMN_ARRAY && toklarge[3].size == 2); @@ -327,27 +328,61 @@ int test_objects_arrays() { js = "[10}"; jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 10); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); check(r == JSMN_ERROR_INVAL); js = "[10]"; jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 10); - check(r == JSMN_SUCCESS); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); + check(r >= 0); js = "{\"a\": 1]"; jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 10); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); check(r == JSMN_ERROR_INVAL); js = "{\"a\": 1}"; jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 10); - check(r == JSMN_SUCCESS); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); + check(r >= 0); return 0; } +int test_issue_22() { + int i; + int r; + jsmn_parser p; + jsmntok_t tokens[128]; + const char *js; + + js = "{ \"height\":10, \"layers\":[ { \"data\":[6,6], \"height\":10, " + "\"name\":\"Calque de Tile 1\", \"opacity\":1, \"type\":\"tilelayer\", " + "\"visible\":true, \"width\":10, \"x\":0, \"y\":0 }], " + "\"orientation\":\"orthogonal\", \"properties\": { }, \"tileheight\":32, " + "\"tilesets\":[ { \"firstgid\":1, \"image\":\"..\\/images\\/tiles.png\", " + "\"imageheight\":64, \"imagewidth\":160, \"margin\":0, \"name\":\"Tiles\", " + "\"properties\":{}, \"spacing\":0, \"tileheight\":32, \"tilewidth\":32 }], " + "\"tilewidth\":32, \"version\":1, \"width\":10 }"; + jsmn_init(&p); + r = jsmn_parse(&p, js, strlen(js), tokens, 128); + check(r >= 0); +#if 0 + for (i = 1; tokens[i].end < tokens[0].end; i++) { + if (tokens[i].type == JSMN_STRING || tokens[i].type == JSMN_PRIMITIVE) { + printf("%.*s\n", tokens[i].end - tokens[i].start, js + tokens[i].start); + } else if (tokens[i].type == JSMN_ARRAY) { + printf("[%d elems]\n", tokens[i].size); + } else if (tokens[i].type == JSMN_OBJECT) { + printf("{%d elems}\n", tokens[i].size); + } else { + TOKEN_PRINT(tokens[i]); + } + } +#endif + return 0; +} + int test_unicode_characters() { jsmn_parser p; jsmntok_t tokens[10]; @@ -356,39 +391,147 @@ int test_unicode_characters() { int r; js = "{\"a\":\"\\uAbcD\"}"; jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 10); - check(r == JSMN_SUCCESS); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); + check(r >= 0); js = "{\"a\":\"str\\u0000\"}"; jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 10); - check(r == JSMN_SUCCESS); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); + check(r >= 0); js = "{\"a\":\"\\uFFFFstr\"}"; jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 10); - check(r == JSMN_SUCCESS); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); + check(r >= 0); js = "{\"a\":\"str\\uFFGFstr\"}"; jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 10); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); check(r == JSMN_ERROR_INVAL); js = "{\"a\":\"str\\u@FfF\"}"; jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 10); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); check(r == JSMN_ERROR_INVAL); js = "{\"a\":[\"\\u028\"]}"; jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 10); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); check(r == JSMN_ERROR_INVAL); js = "{\"a\":[\"\\u0280\"]}"; jsmn_init(&p); - r = jsmn_parse(&p, js, tokens, 10); - check(r == JSMN_SUCCESS); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); + check(r >= 0); + + return 0; +} + +int test_input_length() { + const char *js; + int r; + jsmn_parser p; + jsmntok_t tokens[10]; + + js = "{\"a\": 0}garbage"; + + jsmn_init(&p); + r = jsmn_parse(&p, js, 8, tokens, 10); + check(r == 3); + check(TOKEN_STRING(js, tokens[0], "{\"a\": 0}")); + check(TOKEN_STRING(js, tokens[1], "a")); + check(TOKEN_STRING(js, tokens[2], "0")); + + return 0; +} + +int test_count() { + jsmn_parser p; + const char *js; + + js = "{}"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 1); + + js = "[]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 1); + + js = "[[]]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 2); + + js = "[[], []]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 3); + + js = "[[], []]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 3); + + js = "[[], [[]], [[], []]]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 7); + + js = "[\"a\", [[], []]]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 5); + + js = "[[], \"[], [[]]\", [[]]]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 5); + js = "[1, 2, 3]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 4); + + js = "[1, 2, [3, \"a\"], null]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 7); + + return 0; +} + +/** A huge redefinition of everything to include jsmn in non-script mode */ +#define jsmn_init jsmn_init_nonstrict +#define jsmn_parse jsmn_parse_nonstrict +#define jsmn_parser jsmn_parser_nonstrict +#define jsmn_alloc_token jsmn_alloc_token_nonstrict +#define jsmn_fill_token jsmn_fill_token_nonstrict +#define jsmn_parse_primitive jsmn_parse_primitive_nonstrict +#define jsmn_parse_string jsmn_parse_string_nonstrict +#define jsmntype_t jsmntype_nonstrict_t +#define jsmnerr_t jsmnerr_nonstrict_t +#define jsmntok_t jsmntok_nonstrict_t +#define JSMN_PRIMITIVE JSMN_PRIMITIVE_NONSTRICT +#define JSMN_OBJECT JSMN_OBJECT_NONSTRICT +#define JSMN_ARRAY JSMN_ARRAY_NONSTRICT +#define JSMN_STRING JSMN_STRING_NONSTRICT +#define JSMN_ERROR_NOMEM JSMN_ERROR_NOMEM_NONSTRICT +#define JSMN_ERROR_INVAL JSMN_ERROR_INVAL_NONSTRICT +#define JSMN_ERROR_PART JSMN_ERROR_PART_NONSTRICT +#undef __JSMN_H_ +#undef JSMN_STRICT +#include "jsmn.c" + +int test_nonstrict() { + const char *js; + int r; + jsmn_parser p; + jsmntok_t tokens[10]; + + js = "a: 0garbage"; + + jsmn_init(&p); + r = jsmn_parse(&p, js, 4, tokens, 10); + check(r == 2); + check(TOKEN_STRING(js, tokens[0], "a")); + check(TOKEN_STRING(js, tokens[1], "0")); + + js = "Day : 26\nMonth : Sep\n\nYear: 12"; + jsmn_init(&p); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); + check(r == 6); return 0; } @@ -403,6 +546,10 @@ int main() { test(test_unquoted_keys, "test unquoted keys (like in JavaScript)"); test(test_objects_arrays, "test objects and arrays"); test(test_unicode_characters, "test unicode characters"); + test(test_input_length, "test strings that are not null-terminated"); + test(test_issue_22, "test issue #22"); + test(test_count, "test tokens count estimation"); + test(test_nonstrict, "test for non-strict mode"); printf("\nPASSED: %d\nFAILED: %d\n", test_passed, test_failed); return 0; } -- cgit v1.2.3 From db379ec1248ff8a24c3bb330d46e73898ff3adc6 Mon Sep 17 00:00:00 2001 From: Gabriel Gritsch Date: Fri, 21 Feb 2014 09:53:45 +0100 Subject: Added "extern C" to use it with CPP. --- jsmn.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/jsmn.h b/jsmn.h index 54930ad55..c8f388cd0 100644 --- a/jsmn.h +++ b/jsmn.h @@ -1,6 +1,10 @@ #ifndef __JSMN_H_ #define __JSMN_H_ +#ifdef __cplusplus +extern "C" { +#endif + /** * JSON type identifier. Basic types are: * o Object @@ -62,4 +66,8 @@ void jsmn_init(jsmn_parser *parser); jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, unsigned int num_tokens); +#ifdef __cplusplus +} +#endif + #endif /* __JSMN_H_ */ -- cgit v1.2.3 From 76cd398859b868988d3ab0d663d9ba7c8b35e9d9 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Fri, 21 Feb 2014 11:04:16 +0200 Subject: removed JSMN_SUCCESS value from the readme --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b33113c17..a2824074a 100644 --- a/README.md +++ b/README.md @@ -137,9 +137,14 @@ All job is done by `jsmn_parser` object. You can initialize a new parser using: This will create a parser, that can parse up to 10 JSON tokens from `js` string. Later, you can use `jsmn_parse(&parser)` function to process JSON string with the parser. + +A non-negative value is the number of tokens actually used by the parser. +Passing NULL instead of the tokens array would not store parsing results, but +instead the function will return the value of tokens needed to parse the given +string. This can be useful if you don't know yet how many tokens to allocate. + If something goes wrong, you will get an error. Error will be one of these: -* `JSMN_SUCCESS` - everything went fine. String was parsed * `JSMN_ERROR_INVAL` - bad token, JSON string is corrupted * `JSMN_ERROR_NOMEM` - not enough tokens, JSON string is too large * `JSMN_ERROR_PART` - JSON string is too short, expecting more JSON data -- cgit v1.2.3 From cf39341a39938e304cf0f2371ac97ea4c48da973 Mon Sep 17 00:00:00 2001 From: Wizard Tai Date: Sat, 23 Aug 2014 15:28:58 +0000 Subject: jsmn.h:28:22: warning: commas at the end of enumerator lists are a C99-specific feature [-Wc99-extensions] WITH: -std=c89 -Wall -pedantic -Wno-declaration-after-statement --- jsmn.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsmn.h b/jsmn.h index c8f388cd0..9174980c5 100644 --- a/jsmn.h +++ b/jsmn.h @@ -25,7 +25,7 @@ typedef enum { /* Invalid character inside JSON string */ JSMN_ERROR_INVAL = -2, /* The string is not a full JSON packet, more bytes expected */ - JSMN_ERROR_PART = -3, + JSMN_ERROR_PART = -3 } jsmnerr_t; /** -- cgit v1.2.3 From 8a22e0a14900dfcc4cdcfc71d5e12acff561ce25 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Mon, 17 Nov 2014 14:13:58 +0200 Subject: implemented key/value hierarchy; added 2 examples; fixed some warnings in tests --- Makefile | 10 ++++- example/jsondump.c | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++++ example/simple.c | 75 +++++++++++++++++++++++++++++++++++ jsmn.c | 23 ++++++++++- jsmn_test.c | 4 +- 5 files changed, 219 insertions(+), 5 deletions(-) create mode 100644 example/jsondump.c create mode 100644 example/simple.c diff --git a/Makefile b/Makefile index ac947a3af..fe9e168ca 100644 --- a/Makefile +++ b/Makefile @@ -17,11 +17,19 @@ jsmn_test: jsmn_test.o jsmn_test.o: jsmn_test.c libjsmn.a +simple_example: example/simple.o libjsmn.a + $(CC) $^ -o $@ + +jsondump: example/jsondump.o libjsmn.a + $(CC) $^ -o $@ + clean: - rm -f jsmn.o jsmn_test.o + rm -f jsmn.o jsmn_test.o example/simple.o rm -f jsmn_test rm -f jsmn_test.exe rm -f libjsmn.a + rm -f simple_example + rm -f jsondump .PHONY: all clean test diff --git a/example/jsondump.c b/example/jsondump.c new file mode 100644 index 000000000..3490bbf49 --- /dev/null +++ b/example/jsondump.c @@ -0,0 +1,112 @@ +#include +#include +#include +#include +#include "../jsmn.h" + +/* + * An example of reading JSON from stdin and printing its content to stdout. + * The output looks like YAML, but I'm not sure if it's really compatible. + */ + +static int dump(const char *js, jsmntok_t *t, size_t count, int indent) { + int i, j, k; + if (count == 0) { + return 0; + } + if (t->type == JSMN_PRIMITIVE) { + printf("%.*s", t->end - t->start, js+t->start); + return 1; + } else if (t->type == JSMN_STRING) { + printf("'%.*s'", t->end - t->start, js+t->start); + return 1; + } else if (t->type == JSMN_OBJECT) { + printf("\n"); + j = 0; + for (i = 0; i < t->size; i++) { + for (k = 0; k < indent; k++) printf(" "); + j += dump(js, t+1+j, count-j, indent+1); + printf(": "); + j += dump(js, t+1+j, count-j, indent+1); + printf("\n"); + } + return j+1; + } else if (t->type == JSMN_ARRAY) { + j = 0; + printf("\n"); + for (i = 0; i < t->size; i++) { + for (k = 0; k < indent-1; k++) printf(" "); + printf(" - "); + j += dump(js, t+1+j, count-j, indent+1); + printf("\n"); + } + return j+1; + } + return 0; +} + +int main() { + int r; + int eof_expected = 0; + char *js = NULL; + size_t jslen = 0; + char buf[BUFSIZ]; + + jsmn_parser p; + jsmntok_t *tok; + size_t tokcount = 2; + + /* Prepare parser */ + jsmn_init(&p); + + /* Allocate some tokens as a start */ + tok = malloc(sizeof(*tok) * tokcount); + if (tok == NULL) { + fprintf(stderr, "malloc(): errno=%d\n", errno); + return 3; + } + + for (;;) { + /* Read another chunk */ + r = fread(buf, 1, sizeof(buf), stdin); + if (r < 0) { + fprintf(stderr, "fread(): %d, errno=%d\n", r, errno); + return 1; + } + if (r == 0) { + if (eof_expected != 0) { + return 0; + } else { + fprintf(stderr, "fread(): unexpected EOF\n"); + return 2; + } + } + + js = realloc(js, jslen + r + 1); + if (js == NULL) { + fprintf(stderr, "realloc(): errno=%d\n", errno); + return 3; + } + strncpy(js + jslen, buf, r); + jslen = jslen + r; + +again: + r = jsmn_parse(&p, js, jslen, tok, tokcount); + if (r < 0) { + if (r == JSMN_ERROR_NOMEM) { + tokcount = tokcount * 2; + tok = realloc(tok, sizeof(*tok) * tokcount); + if (tok == NULL) { + fprintf(stderr, "realloc(): errno=%d\n", errno); + return 3; + } + goto again; + } + } else { + dump(js, tok, p.toknext, 0); + eof_expected = 1; + } + } + + return 0; +} diff --git a/example/simple.c b/example/simple.c new file mode 100644 index 000000000..a6f8e6a98 --- /dev/null +++ b/example/simple.c @@ -0,0 +1,75 @@ +#include +#include +#include "../jsmn.h" + +/* + * A small example of jsmn parsing when JSON structure is known and number of + * tokens is predictable. + */ + +const char *JSON_STRING = + "{\"user\": \"johndoe\", \"admin\": false, \"uid\": 1000,\n " + "\"groups\": [\"users\", \"wheel\", \"audio\", \"video\"]}"; + +static int jsoneq(const char *json, jsmntok_t *tok, const char *s) { + if (tok->type == JSMN_STRING && (int) strlen(s) == tok->end - tok->start && + strncmp(json + tok->start, s, tok->end - tok->start) == 0) { + return 0; + } + return -1; +} + +int main() { + int i; + int r; + jsmn_parser p; + jsmntok_t t[128]; /* We expect no more than 128 tokens */ + + jsmn_init(&p); + r = jsmn_parse(&p, JSON_STRING, strlen(JSON_STRING), t, sizeof(t)/sizeof(t[0])); + if (r < 0) { + printf("Failed to parse JSON: %d\n", r); + return 1; + } + + /* Assume the top-level element is an object */ + if (r < 1 || t[0].type != JSMN_OBJECT) { + printf("Object expected\n"); + return 1; + } + + /* Loop over all keys of the root object */ + for (i = 1; i < r; i++) { + if (jsoneq(JSON_STRING, &t[i], "user") == 0) { + /* We may use strndup() to fetch string value */ + printf("- User: %.*s\n", t[i+1].end-t[i+1].start, + JSON_STRING + t[i+1].start); + i++; + } else if (jsoneq(JSON_STRING, &t[i], "admin") == 0) { + /* We may additionally check if the value is either "true" or "false" */ + printf("- Admin: %.*s\n", t[i+1].end-t[i+1].start, + JSON_STRING + t[i+1].start); + i++; + } else if (jsoneq(JSON_STRING, &t[i], "uid") == 0) { + /* We may want to do strtol() here to get numeric value */ + printf("- UID: %.*s\n", t[i+1].end-t[i+1].start, + JSON_STRING + t[i+1].start); + i++; + } else if (jsoneq(JSON_STRING, &t[i], "groups") == 0) { + int j; + printf("- Groups:\n"); + if (t[i+1].type != JSMN_ARRAY) { + continue; /* We expect groups to be an array of strings */ + } + for (j = 0; j < t[i+1].size; j++) { + jsmntok_t *g = &t[i+j+2]; + printf(" * %.*s\n", g->end - g->start, JSON_STRING + g->start); + } + i += t[i+1].size + 1; + } else { + printf("Unexpected key: %.*s\n", t[i].end-t[i].start, + JSON_STRING + t[i].start); + } + } + return 0; +} diff --git a/jsmn.c b/jsmn.c index 563813965..893f23cb1 100644 --- a/jsmn.c +++ b/jsmn.c @@ -234,7 +234,28 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t len, if (parser->toksuper != -1 && tokens != NULL) tokens[parser->toksuper].size++; break; - case '\t' : case '\r' : case '\n' : case ':' : case ',': case ' ': + case '\t' : case '\r' : case '\n' : case ' ': + break; + case ':': + parser->toksuper = parser->toknext - 1; + break; + case ',': + if (tokens != NULL && + tokens[parser->toksuper].type != JSMN_ARRAY && + tokens[parser->toksuper].type != JSMN_OBJECT) { +#ifdef JSMN_PARENT_LINKS + parser->toksuper = tokens[parser->toksuper].parent; +#else + for (i = parser->toknext - 1; i >= 0; i--) { + if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { + if (tokens[i].start != -1 && tokens[i].end == -1) { + parser->toksuper = i; + break; + } + } + } +#endif + } break; #ifdef JSMN_STRICT /* In strict mode primitives are: numbers and booleans */ diff --git a/jsmn_test.c b/jsmn_test.c index c5bfc0354..c9c47db85 100644 --- a/jsmn_test.c +++ b/jsmn_test.c @@ -112,11 +112,11 @@ int test_simple() { } int test_primitive() { +#ifndef JSMN_STRICT int r; jsmn_parser p; jsmntok_t tok[10]; const char *js; -#ifndef JSMN_STRICT js = "\"boolVar\" : true"; jsmn_init(&p); r = jsmn_parse(&p, js, strlen(js), tok, 10); @@ -320,7 +320,6 @@ int test_array_nomem() { } int test_objects_arrays() { - int i; int r; jsmn_parser p; jsmntok_t tokens[10]; @@ -350,7 +349,6 @@ int test_objects_arrays() { } int test_issue_22() { - int i; int r; jsmn_parser p; jsmntok_t tokens[128]; -- cgit v1.2.3 From 838061aa96bf9a756b3ae744787064b3201da914 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Mon, 17 Nov 2014 14:14:45 +0200 Subject: removed trailing spaces --- jsmn.c | 8 ++++---- jsmn_test.c | 28 ++++++++++++++-------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/jsmn.c b/jsmn.c index 893f23cb1..e22d8a3c9 100644 --- a/jsmn.c +++ b/jsmn.c @@ -5,7 +5,7 @@ /** * Allocates a fresh unused token from the token pull. */ -static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, +static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens, size_t num_tokens) { jsmntok_t *tok; if (parser->toknext >= num_tokens) { @@ -23,7 +23,7 @@ static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, /** * Fills token type and boundaries. */ -static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, +static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, int start, int end) { token->type = type; token->start = start; @@ -234,7 +234,7 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t len, if (parser->toksuper != -1 && tokens != NULL) tokens[parser->toksuper].size++; break; - case '\t' : case '\r' : case '\n' : case ' ': + case '\t' : case '\r' : case '\n' : case ' ': break; case ':': parser->toksuper = parser->toknext - 1; @@ -292,7 +292,7 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t len, } /** - * Creates a new parser based over a given buffer with an array of tokens + * Creates a new parser based over a given buffer with an array of tokens * available. */ void jsmn_init(jsmn_parser *parser) { diff --git a/jsmn_test.c b/jsmn_test.c index c9c47db85..7fbcee1b7 100644 --- a/jsmn_test.c +++ b/jsmn_test.c @@ -120,7 +120,7 @@ int test_primitive() { js = "\"boolVar\" : true"; jsmn_init(&p); r = jsmn_parse(&p, js, strlen(js), tok, 10); - check(r >= 0 && tok[0].type == JSMN_STRING + check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_PRIMITIVE); check(TOKEN_STRING(js, tok[0], "boolVar")); check(TOKEN_STRING(js, tok[1], "true")); @@ -128,7 +128,7 @@ int test_primitive() { js = "\"boolVar\" : false"; jsmn_init(&p); r = jsmn_parse(&p, js, strlen(js), tok, 10); - check(r >= 0 && tok[0].type == JSMN_STRING + check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_PRIMITIVE); check(TOKEN_STRING(js, tok[0], "boolVar")); check(TOKEN_STRING(js, tok[1], "false")); @@ -136,7 +136,7 @@ int test_primitive() { js = "\"intVar\" : 12345"; jsmn_init(&p); r = jsmn_parse(&p, js, strlen(js), tok, 10); - check(r >= 0 && tok[0].type == JSMN_STRING + check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_PRIMITIVE); check(TOKEN_STRING(js, tok[0], "intVar")); check(TOKEN_STRING(js, tok[1], "12345")); @@ -144,7 +144,7 @@ int test_primitive() { js = "\"floatVar\" : 12.345"; jsmn_init(&p); r = jsmn_parse(&p, js, strlen(js), tok, 10); - check(r >= 0 && tok[0].type == JSMN_STRING + check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_PRIMITIVE); check(TOKEN_STRING(js, tok[0], "floatVar")); check(TOKEN_STRING(js, tok[1], "12.345")); @@ -152,7 +152,7 @@ int test_primitive() { js = "\"nullVar\" : null"; jsmn_init(&p); r = jsmn_parse(&p, js, strlen(js), tok, 10); - check(r >= 0 && tok[0].type == JSMN_STRING + check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_PRIMITIVE); check(TOKEN_STRING(js, tok[0], "nullVar")); check(TOKEN_STRING(js, tok[1], "null")); @@ -169,7 +169,7 @@ int test_string() { js = "\"strVar\" : \"hello world\""; jsmn_init(&p); r = jsmn_parse(&p, js, strlen(js), tok, 10); - check(r >= 0 && tok[0].type == JSMN_STRING + check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_STRING); check(TOKEN_STRING(js, tok[0], "strVar")); check(TOKEN_STRING(js, tok[1], "hello world")); @@ -177,7 +177,7 @@ int test_string() { js = "\"strVar\" : \"escapes: \\/\\r\\n\\t\\b\\f\\\"\\\\\""; jsmn_init(&p); r = jsmn_parse(&p, js, strlen(js), tok, 10); - check(r >= 0 && tok[0].type == JSMN_STRING + check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_STRING); check(TOKEN_STRING(js, tok[0], "strVar")); check(TOKEN_STRING(js, tok[1], "escapes: \\/\\r\\n\\t\\b\\f\\\"\\\\")); @@ -185,7 +185,7 @@ int test_string() { js = "\"strVar\" : \"\""; jsmn_init(&p); r = jsmn_parse(&p, js, strlen(js), tok, 10); - check(r >= 0 && tok[0].type == JSMN_STRING + check(r >= 0 && tok[0].type == JSMN_STRING && tok[1].type == JSMN_STRING); check(TOKEN_STRING(js, tok[0], "strVar")); check(TOKEN_STRING(js, tok[1], "")); @@ -263,18 +263,18 @@ int test_partial_array() { jsmn_init(&p); js = " [ 1, true, "; r = jsmn_parse(&p, js, strlen(js), tok, 10); - check(r == JSMN_ERROR_PART && tok[0].type == JSMN_ARRAY + check(r == JSMN_ERROR_PART && tok[0].type == JSMN_ARRAY && tok[1].type == JSMN_PRIMITIVE && tok[2].type == JSMN_PRIMITIVE); js = " [ 1, true, [123, \"hello"; r = jsmn_parse(&p, js, strlen(js), tok, 10); - check(r == JSMN_ERROR_PART && tok[0].type == JSMN_ARRAY + check(r == JSMN_ERROR_PART && tok[0].type == JSMN_ARRAY && tok[1].type == JSMN_PRIMITIVE && tok[2].type == JSMN_PRIMITIVE && tok[3].type == JSMN_ARRAY && tok[4].type == JSMN_PRIMITIVE); js = " [ 1, true, [123, \"hello\"]"; r = jsmn_parse(&p, js, strlen(js), tok, 10); - check(r == JSMN_ERROR_PART && tok[0].type == JSMN_ARRAY + check(r == JSMN_ERROR_PART && tok[0].type == JSMN_ARRAY && tok[1].type == JSMN_PRIMITIVE && tok[2].type == JSMN_PRIMITIVE && tok[3].type == JSMN_ARRAY && tok[4].type == JSMN_PRIMITIVE && tok[5].type == JSMN_STRING); @@ -283,7 +283,7 @@ int test_partial_array() { js = " [ 1, true, [123, \"hello\"]]"; r = jsmn_parse(&p, js, strlen(js), tok, 10); - check(r >= 0 && tok[0].type == JSMN_ARRAY + check(r >= 0 && tok[0].type == JSMN_ARRAY && tok[1].type == JSMN_PRIMITIVE && tok[2].type == JSMN_PRIMITIVE && tok[3].type == JSMN_ARRAY && tok[4].type == JSMN_PRIMITIVE && tok[5].type == JSMN_STRING); @@ -470,7 +470,7 @@ int test_count() { js = "[[], [[]], [[], []]]"; jsmn_init(&p); check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 7); - + js = "[\"a\", [[], []]]"; jsmn_init(&p); check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 5); @@ -486,7 +486,7 @@ int test_count() { js = "[1, 2, [3, \"a\"], null]"; jsmn_init(&p); check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 7); - + return 0; } -- cgit v1.2.3 From a0e2b876cacf1dae415dd331e0589b1832ae2052 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Mon, 17 Nov 2014 14:28:27 +0200 Subject: including stddef.h in the jsmn.h, fixed issue #28 --- jsmn.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jsmn.h b/jsmn.h index 9174980c5..95fb2cabd 100644 --- a/jsmn.h +++ b/jsmn.h @@ -1,6 +1,8 @@ #ifndef __JSMN_H_ #define __JSMN_H_ +#include + #ifdef __cplusplus extern "C" { #endif -- cgit v1.2.3 From 946a2b1e02665a6c0235ba26c432cf09fe598cce Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Mon, 17 Nov 2014 15:06:12 +0200 Subject: moved i declaration to the top of the block, issue #38 fixed --- jsmn.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsmn.c b/jsmn.c index e22d8a3c9..412c3c251 100644 --- a/jsmn.c +++ b/jsmn.c @@ -114,6 +114,7 @@ static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, /* Backslash: Quoted symbol expected */ if (c == '\\') { + int i = 0; parser->pos++; switch (js[parser->pos]) { /* Allowed escaped symbols */ @@ -123,7 +124,6 @@ static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, /* Allows escaped symbol \uXXXX */ case 'u': parser->pos++; - int i = 0; for(; i < 4 && js[parser->pos] != '\0'; i++) { /* If it isn't a hex character we have an error */ if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ -- cgit v1.2.3 From 91d7389ec80c742a589bfed25e62aa5f95d2f0f9 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Mon, 17 Nov 2014 15:36:18 +0200 Subject: added some tests for key/values nodes and added some stricter validations --- jsmn.c | 8 ++++++++ jsmn_test.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/jsmn.c b/jsmn.c index 412c3c251..83353bd01 100644 --- a/jsmn.c +++ b/jsmn.c @@ -262,6 +262,14 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t len, case '-': case '0': case '1' : case '2': case '3' : case '4': case '5': case '6': case '7' : case '8': case '9': case 't': case 'f': case 'n' : + /* And they must not be keys of the object */ + if (tokens != NULL) { + jsmntok_t *t = &tokens[parser->toksuper]; + if (t->type == JSMN_OBJECT || + (t->type == JSMN_STRING && t->size != 0)) { + return JSMN_ERROR_INVAL; + } + } #else /* In non-strict mode every unquoted value is a primitive */ default: diff --git a/jsmn_test.c b/jsmn_test.c index 7fbcee1b7..36d04e363 100644 --- a/jsmn_test.c +++ b/jsmn_test.c @@ -490,6 +490,49 @@ int test_count() { return 0; } +int test_keyvalue() { + const char *js; + int r; + jsmn_parser p; + jsmntok_t tokens[10]; + + js = "{\"a\": 0, \"b\": \"c\"}"; + + jsmn_init(&p); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); + check(r == 5); + check(tokens[0].size == 2); /* two keys */ + check(tokens[1].size == 1 && tokens[3].size == 1); /* one value per key */ + check(tokens[2].size == 0 && tokens[4].size == 0); /* values have zero size */ + + js = "{\"a\"\n0}"; + jsmn_init(&p); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); + check(r == JSMN_ERROR_INVAL); + + js = "{\"a\", 0}"; + jsmn_init(&p); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); + check(r == JSMN_ERROR_INVAL); + + js = "{\"a\": {2}}"; + jsmn_init(&p); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); + check(r == JSMN_ERROR_INVAL); + + js = "{\"a\": {2: 3}}"; + jsmn_init(&p); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); + check(r == JSMN_ERROR_INVAL); + + + js = "{\"a\": {\"a\": 2 3}}"; + jsmn_init(&p); + r = jsmn_parse(&p, js, strlen(js), tokens, 10); + check(r == JSMN_ERROR_INVAL); + return 0; +} + /** A huge redefinition of everything to include jsmn in non-script mode */ #define jsmn_init jsmn_init_nonstrict #define jsmn_parse jsmn_parse_nonstrict @@ -548,6 +591,7 @@ int main() { test(test_issue_22, "test issue #22"); test(test_count, "test tokens count estimation"); test(test_nonstrict, "test for non-strict mode"); + test(test_keyvalue, "test for keys/values"); printf("\nPASSED: %d\nFAILED: %d\n", test_passed, test_failed); return 0; } -- cgit v1.2.3 From cf38b7d17157ba674199238da776d822b184c154 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Mon, 17 Nov 2014 16:21:36 +0200 Subject: added js string boundaries checks for string parser, fixes issue #31; added tests to cover it; fixed makefile to use custom cflags/ldflags --- Makefile | 6 +++--- jsmn.c | 6 +++--- jsmn_test.c | 10 ++++++++++ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index fe9e168ca..5e3e2a97f 100644 --- a/Makefile +++ b/Makefile @@ -13,15 +13,15 @@ test: jsmn_test ./jsmn_test jsmn_test: jsmn_test.o - $(CC) -L. -ljsmn $< -o $@ + $(CC) $(LDFLAGS) -L. -ljsmn $< -o $@ jsmn_test.o: jsmn_test.c libjsmn.a simple_example: example/simple.o libjsmn.a - $(CC) $^ -o $@ + $(CC) $(LDFLAGS) $^ -o $@ jsondump: example/jsondump.o libjsmn.a - $(CC) $^ -o $@ + $(CC) $(LDFLAGS) $^ -o $@ clean: rm -f jsmn.o jsmn_test.o example/simple.o diff --git a/jsmn.c b/jsmn.c index 83353bd01..a0f4f69c6 100644 --- a/jsmn.c +++ b/jsmn.c @@ -113,8 +113,8 @@ static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, } /* Backslash: Quoted symbol expected */ - if (c == '\\') { - int i = 0; + if (c == '\\' && parser->pos + 1 < len) { + int i; parser->pos++; switch (js[parser->pos]) { /* Allowed escaped symbols */ @@ -124,7 +124,7 @@ static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, /* Allows escaped symbol \uXXXX */ case 'u': parser->pos++; - for(; i < 4 && js[parser->pos] != '\0'; i++) { + for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) { /* If it isn't a hex character we have an error */ if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ diff --git a/jsmn_test.c b/jsmn_test.c index 36d04e363..396885922 100644 --- a/jsmn_test.c +++ b/jsmn_test.c @@ -206,6 +206,16 @@ int test_partial_string() { check(TOKEN_STRING(js, tok[0], "x")); check(p.toknext == 1); + jsmn_init(&p); + char js_slash[9] = "\"x\": \"va\\"; + r = jsmn_parse(&p, js_slash, sizeof(js_slash), tok, 10); + check(r == JSMN_ERROR_PART); + + jsmn_init(&p); + char js_unicode[10] = "\"x\": \"va\\u"; + r = jsmn_parse(&p, js_unicode, sizeof(js_unicode), tok, 10); + check(r == JSMN_ERROR_PART); + js = "\"x\": \"valu"; r = jsmn_parse(&p, js, strlen(js), tok, 10); check(r == JSMN_ERROR_PART && tok[0].type == JSMN_STRING); -- cgit v1.2.3 From 26193d39c2d17bf184b691ebfa406b5b9782577a Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Fri, 19 Dec 2014 01:40:26 +0200 Subject: updated README --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a2824074a..353af94a8 100644 --- a/README.md +++ b/README.md @@ -126,19 +126,21 @@ to simplify string extraction from JSON data. All job is done by `jsmn_parser` object. You can initialize a new parser using: - struct jsmn_parser parser; + jsmn_parser parser; jsmntok_t tokens[10]; + jsmn_init(&parser); + // js - pointer to JSON string // tokens - an array of tokens available // 10 - number of tokens available - jsmn_init_parser(&parser, js, tokens, 10); - -This will create a parser, that can parse up to 10 JSON tokens from `js` string. + jsmn_parse(&parser, js, tokens, 10); -Later, you can use `jsmn_parse(&parser)` function to process JSON string with the parser. +This will create a parser, and then it tries to parse up to 10 JSON tokens from +the `js` string. -A non-negative value is the number of tokens actually used by the parser. +A non-negative reutrn value of `jsmn_parse` is the number of tokens actually +used by the parser. Passing NULL instead of the tokens array would not store parsing results, but instead the function will return the value of tokens needed to parse the given string. This can be useful if you don't know yet how many tokens to allocate. -- cgit v1.2.3 From bd9bd55c3a79a1f1f24d1bbf1c4a12e40a4810c8 Mon Sep 17 00:00:00 2001 From: Serge Zaitsev Date: Thu, 24 Sep 2015 18:22:14 +0200 Subject: Changed links to github --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 353af94a8..c7837f752 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,10 @@ easily integrated into resource-limited or embedded projects. You can find more information about JSON format at [json.org][1] -Library sources are available at [bitbucket.org/zserge/jsmn][2] +Library sources are available at https://github.com/zserge/jsmn The web page with some information about jsmn can be found at -[http://zserge.com/jsmn.html][3] +[http://zserge.com/jsmn.html][2] Philosophy ---------- @@ -78,9 +78,9 @@ it possible to use zero-copy techniques. Install ------- -To clone the repository you should have mercurial installed. Just run: +To clone the repository you should have Git installed. Just run: - $ hg clone http://bitbucket.org/zserge/jsmn jsmn + $ git clone https://github.com/zserge/jsmn Repository layout is simple: jsmn.c and jsmn.h are library files, tests are in the jsmn\_test.c, you will also find README, LICENSE and Makefile files inside. @@ -163,5 +163,4 @@ This software is distributed under [MIT license](http://www.opensource.org/licen so feel free to integrate it in your commercial products. [1]: http://www.json.org/ -[2]: https://bitbucket.org/zserge/jsmn/wiki/Home -[3]: http://zserge.com/jsmn.html +[2]: http://zserge.com/jsmn.html -- cgit v1.2.3 From 6cb9c3721dfd7fab2bd63df9683b6fed67f86212 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Sat, 17 Oct 2015 10:29:54 +0200 Subject: test exit status depends on number of failed tests --- jsmn_test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsmn_test.c b/jsmn_test.c index 396885922..9c4a22e7d 100644 --- a/jsmn_test.c +++ b/jsmn_test.c @@ -603,6 +603,6 @@ int main() { test(test_nonstrict, "test for non-strict mode"); test(test_keyvalue, "test for keys/values"); printf("\nPASSED: %d\nFAILED: %d\n", test_passed, test_failed); - return 0; + return (test_failed > 0); } -- cgit v1.2.3 From 6809c029aa0f194bb7bb03a9e2fe70e729bf209b Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Sat, 17 Oct 2015 10:39:27 +0200 Subject: changed test function args to void, fixes #46 --- jsmn_test.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/jsmn_test.c b/jsmn_test.c index 9c4a22e7d..1510cf551 100644 --- a/jsmn_test.c +++ b/jsmn_test.c @@ -41,7 +41,7 @@ static void test(int (*func)(void), const char *name) { #define JSMN_STRICT #include "jsmn.c" -int test_empty() { +int test_empty(void) { const char *js; int r; jsmn_parser p; @@ -79,7 +79,7 @@ int test_empty() { return 0; } -int test_simple() { +int test_simple(void) { const char *js; int r; jsmn_parser p; @@ -111,7 +111,7 @@ int test_simple() { return 0; } -int test_primitive() { +int test_primitive(void) { #ifndef JSMN_STRICT int r; jsmn_parser p; @@ -160,7 +160,7 @@ int test_primitive() { return 0; } -int test_string() { +int test_string(void) { int r; jsmn_parser p; jsmntok_t tok[10]; @@ -193,7 +193,7 @@ int test_string() { return 0; } -int test_partial_string() { +int test_partial_string(void) { int r; jsmn_parser p; jsmntok_t tok[10]; @@ -242,7 +242,7 @@ int test_partial_string() { return 0; } -int test_unquoted_keys() { +int test_unquoted_keys(void) { #ifndef JSMN_STRICT int r; jsmn_parser p; @@ -264,7 +264,7 @@ int test_unquoted_keys() { return 0; } -int test_partial_array() { +int test_partial_array(void) { int r; jsmn_parser p; jsmntok_t tok[10]; @@ -302,7 +302,7 @@ int test_partial_array() { return 0; } -int test_array_nomem() { +int test_array_nomem(void) { int i; int r; jsmn_parser p; @@ -329,7 +329,7 @@ int test_array_nomem() { return 0; } -int test_objects_arrays() { +int test_objects_arrays(void) { int r; jsmn_parser p; jsmntok_t tokens[10]; @@ -358,7 +358,7 @@ int test_objects_arrays() { return 0; } -int test_issue_22() { +int test_issue_22(void) { int r; jsmn_parser p; jsmntok_t tokens[128]; @@ -391,7 +391,7 @@ int test_issue_22() { return 0; } -int test_unicode_characters() { +int test_unicode_characters(void) { jsmn_parser p; jsmntok_t tokens[10]; const char *js; @@ -435,7 +435,7 @@ int test_unicode_characters() { return 0; } -int test_input_length() { +int test_input_length(void) { const char *js; int r; jsmn_parser p; @@ -453,7 +453,7 @@ int test_input_length() { return 0; } -int test_count() { +int test_count(void) { jsmn_parser p; const char *js; @@ -500,7 +500,7 @@ int test_count() { return 0; } -int test_keyvalue() { +int test_keyvalue(void) { const char *js; int r; jsmn_parser p; @@ -565,7 +565,7 @@ int test_keyvalue() { #undef JSMN_STRICT #include "jsmn.c" -int test_nonstrict() { +int test_nonstrict(void) { const char *js; int r; jsmn_parser p; @@ -586,7 +586,7 @@ int test_nonstrict() { return 0; } -int main() { +int main(void) { test(test_empty, "general test for a empty JSON objects/arrays"); test(test_simple, "general test for a simple JSON string"); test(test_primitive, "test primitive JSON data types"); -- cgit v1.2.3 From d87c22d2cd38bb4065d2b33cb683792c2dc9eed3 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Sat, 17 Oct 2015 10:42:30 +0200 Subject: removed mixed declarations and code, fixes #45 --- jsmn_test.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jsmn_test.c b/jsmn_test.c index 1510cf551..3345790c0 100644 --- a/jsmn_test.c +++ b/jsmn_test.c @@ -207,13 +207,13 @@ int test_partial_string(void) { check(p.toknext == 1); jsmn_init(&p); - char js_slash[9] = "\"x\": \"va\\"; - r = jsmn_parse(&p, js_slash, sizeof(js_slash), tok, 10); + js = "\"x\": \"va\\"; + r = jsmn_parse(&p, js, 9, tok, 10); check(r == JSMN_ERROR_PART); jsmn_init(&p); - char js_unicode[10] = "\"x\": \"va\\u"; - r = jsmn_parse(&p, js_unicode, sizeof(js_unicode), tok, 10); + js = "\"x\": \"va\\u"; + r = jsmn_parse(&p, js, 10, tok, 10); check(r == JSMN_ERROR_PART); js = "\"x\": \"valu"; -- cgit v1.2.3 From 226f318224e772edf3109da3af1d283e6dee3d57 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Sat, 17 Oct 2015 10:50:22 +0200 Subject: json token type enum doesn't start with zero value anymore, fixes #24 --- jsmn.h | 5 +++-- jsmn_test.c | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/jsmn.h b/jsmn.h index 95fb2cabd..59cbaa2be 100644 --- a/jsmn.h +++ b/jsmn.h @@ -15,10 +15,11 @@ extern "C" { * o Other primitive: number, boolean (true/false) or null */ typedef enum { - JSMN_PRIMITIVE = 0, + JSMN_UNDEFINED = 0, JSMN_OBJECT = 1, JSMN_ARRAY = 2, - JSMN_STRING = 3 + JSMN_STRING = 3, + JSMN_PRIMITIVE = 4 } jsmntype_t; typedef enum { diff --git a/jsmn_test.c b/jsmn_test.c index 3345790c0..15e522705 100644 --- a/jsmn_test.c +++ b/jsmn_test.c @@ -554,10 +554,11 @@ int test_keyvalue(void) { #define jsmntype_t jsmntype_nonstrict_t #define jsmnerr_t jsmnerr_nonstrict_t #define jsmntok_t jsmntok_nonstrict_t -#define JSMN_PRIMITIVE JSMN_PRIMITIVE_NONSTRICT #define JSMN_OBJECT JSMN_OBJECT_NONSTRICT #define JSMN_ARRAY JSMN_ARRAY_NONSTRICT #define JSMN_STRING JSMN_STRING_NONSTRICT +#define JSMN_PRIMITIVE JSMN_PRIMITIVE_NONSTRICT +#define JSMN_UNDEFINED JSMN_UNDEFINED_NONSTRICT #define JSMN_ERROR_NOMEM JSMN_ERROR_NOMEM_NONSTRICT #define JSMN_ERROR_INVAL JSMN_ERROR_INVAL_NONSTRICT #define JSMN_ERROR_PART JSMN_ERROR_PART_NONSTRICT -- cgit v1.2.3 From 9b4e33199f16fe0dd764db3622ab772e4764e0e0 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Sat, 17 Oct 2015 14:58:31 +0200 Subject: moved tests into a subfolder, added table-driven tests --- Makefile | 20 +- jsmn_test.c | 609 -------------------------------------------------------- test/test.h | 27 +++ test/tests.c | 362 +++++++++++++++++++++++++++++++++ test/testutil.h | 94 +++++++++ 5 files changed, 496 insertions(+), 616 deletions(-) delete mode 100644 jsmn_test.c create mode 100644 test/test.h create mode 100644 test/tests.c create mode 100644 test/testutil.h diff --git a/Makefile b/Makefile index 5e3e2a97f..a382d64d8 100644 --- a/Makefile +++ b/Makefile @@ -9,11 +9,19 @@ libjsmn.a: jsmn.o %.o: %.c jsmn.h $(CC) -c $(CFLAGS) $< -o $@ -test: jsmn_test - ./jsmn_test - -jsmn_test: jsmn_test.o - $(CC) $(LDFLAGS) -L. -ljsmn $< -o $@ +test: test_default test_strict test_links test_strict_links +test_default: test/tests.c + $(CC) $(CFLAGS) $(LDFLAGS) $< -o test/$@ + ./test/$@ +test_strict: test/tests.c + $(CC) -DJSMN_STRICT=1 $(CFLAGS) $(LDFLAGS) $< -o test/$@ + ./test/$@ +test_links: test/tests.c + $(CC) -DJSMN_PARENT_LINKS=1 $(CFLAGS) $(LDFLAGS) $< -o test/$@ + ./test/$@ +test_strict_links: test/tests.c + $(CC) -DJSMN_STRICT=1 -DJSMN_PARENT_LINKS=1 $(CFLAGS) $(LDFLAGS) $< -o test/$@ + ./test/$@ jsmn_test.o: jsmn_test.c libjsmn.a @@ -25,8 +33,6 @@ jsondump: example/jsondump.o libjsmn.a clean: rm -f jsmn.o jsmn_test.o example/simple.o - rm -f jsmn_test - rm -f jsmn_test.exe rm -f libjsmn.a rm -f simple_example rm -f jsondump diff --git a/jsmn_test.c b/jsmn_test.c deleted file mode 100644 index 15e522705..000000000 --- a/jsmn_test.c +++ /dev/null @@ -1,609 +0,0 @@ -#include -#include -#include - -static int test_passed = 0; -static int test_failed = 0; - -/* Terminate current test with error */ -#define fail() return __LINE__ - -/* Successfull end of the test case */ -#define done() return 0 - -/* Check single condition */ -#define check(cond) do { if (!(cond)) fail(); } while (0) - -/* Test runner */ -static void test(int (*func)(void), const char *name) { - int r = func(); - if (r == 0) { - test_passed++; - } else { - test_failed++; - printf("FAILED: %s (at line %d)\n", name, r); - } -} - -#define TOKEN_EQ(t, tok_start, tok_end, tok_type) \ - ((t).start == tok_start \ - && (t).end == tok_end \ - && (t).type == (tok_type)) - -#define TOKEN_STRING(js, t, s) \ - (strncmp(js+(t).start, s, (t).end - (t).start) == 0 \ - && strlen(s) == (t).end - (t).start) - -#define TOKEN_PRINT(t) \ - printf("start: %d, end: %d, type: %d, size: %d\n", \ - (t).start, (t).end, (t).type, (t).size) - -#define JSMN_STRICT -#include "jsmn.c" - -int test_empty(void) { - const char *js; - int r; - jsmn_parser p; - jsmntok_t t[10]; - - js = "{}"; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), t, 10); - check(r >= 0); - check(t[0].type == JSMN_OBJECT); - check(t[0].start == 0 && t[0].end == 2); - - js = "[]"; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), t, 10); - check(r >= 0); - check(t[0].type == JSMN_ARRAY); - check(t[0].start == 0 && t[0].end == 2); - - js = "{\"a\":[]}"; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), t, 10); - check(r >= 0); - check(t[0].type == JSMN_OBJECT && t[0].start == 0 && t[0].end == 8); - check(t[1].type == JSMN_STRING && t[1].start == 2 && t[1].end == 3); - check(t[2].type == JSMN_ARRAY && t[2].start == 5 && t[2].end == 7); - - js = "[{},{}]"; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), t, 10); - check(r >= 0); - check(t[0].type == JSMN_ARRAY && t[0].start == 0 && t[0].end == 7); - check(t[1].type == JSMN_OBJECT && t[1].start == 1 && t[1].end == 3); - check(t[2].type == JSMN_OBJECT && t[2].start == 4 && t[2].end == 6); - return 0; -} - -int test_simple(void) { - const char *js; - int r; - jsmn_parser p; - jsmntok_t tokens[10]; - - js = "{\"a\": 0}"; - - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), tokens, 10); - check(r >= 0); - check(TOKEN_EQ(tokens[0], 0, 8, JSMN_OBJECT)); - check(TOKEN_EQ(tokens[1], 2, 3, JSMN_STRING)); - check(TOKEN_EQ(tokens[2], 6, 7, JSMN_PRIMITIVE)); - - check(TOKEN_STRING(js, tokens[0], js)); - check(TOKEN_STRING(js, tokens[1], "a")); - check(TOKEN_STRING(js, tokens[2], "0")); - - jsmn_init(&p); - js = "[\"a\":{},\"b\":{}]"; - r = jsmn_parse(&p, js, strlen(js), tokens, 10); - check(r >= 0); - - jsmn_init(&p); - js = "{\n \"Day\": 26,\n \"Month\": 9,\n \"Year\": 12\n }"; - r = jsmn_parse(&p, js, strlen(js), tokens, 10); - check(r >= 0); - - return 0; -} - -int test_primitive(void) { -#ifndef JSMN_STRICT - int r; - jsmn_parser p; - jsmntok_t tok[10]; - const char *js; - js = "\"boolVar\" : true"; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), tok, 10); - check(r >= 0 && tok[0].type == JSMN_STRING - && tok[1].type == JSMN_PRIMITIVE); - check(TOKEN_STRING(js, tok[0], "boolVar")); - check(TOKEN_STRING(js, tok[1], "true")); - - js = "\"boolVar\" : false"; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), tok, 10); - check(r >= 0 && tok[0].type == JSMN_STRING - && tok[1].type == JSMN_PRIMITIVE); - check(TOKEN_STRING(js, tok[0], "boolVar")); - check(TOKEN_STRING(js, tok[1], "false")); - - js = "\"intVar\" : 12345"; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), tok, 10); - check(r >= 0 && tok[0].type == JSMN_STRING - && tok[1].type == JSMN_PRIMITIVE); - check(TOKEN_STRING(js, tok[0], "intVar")); - check(TOKEN_STRING(js, tok[1], "12345")); - - js = "\"floatVar\" : 12.345"; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), tok, 10); - check(r >= 0 && tok[0].type == JSMN_STRING - && tok[1].type == JSMN_PRIMITIVE); - check(TOKEN_STRING(js, tok[0], "floatVar")); - check(TOKEN_STRING(js, tok[1], "12.345")); - - js = "\"nullVar\" : null"; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), tok, 10); - check(r >= 0 && tok[0].type == JSMN_STRING - && tok[1].type == JSMN_PRIMITIVE); - check(TOKEN_STRING(js, tok[0], "nullVar")); - check(TOKEN_STRING(js, tok[1], "null")); -#endif - return 0; -} - -int test_string(void) { - int r; - jsmn_parser p; - jsmntok_t tok[10]; - const char *js; - - js = "\"strVar\" : \"hello world\""; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), tok, 10); - check(r >= 0 && tok[0].type == JSMN_STRING - && tok[1].type == JSMN_STRING); - check(TOKEN_STRING(js, tok[0], "strVar")); - check(TOKEN_STRING(js, tok[1], "hello world")); - - js = "\"strVar\" : \"escapes: \\/\\r\\n\\t\\b\\f\\\"\\\\\""; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), tok, 10); - check(r >= 0 && tok[0].type == JSMN_STRING - && tok[1].type == JSMN_STRING); - check(TOKEN_STRING(js, tok[0], "strVar")); - check(TOKEN_STRING(js, tok[1], "escapes: \\/\\r\\n\\t\\b\\f\\\"\\\\")); - - js = "\"strVar\" : \"\""; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), tok, 10); - check(r >= 0 && tok[0].type == JSMN_STRING - && tok[1].type == JSMN_STRING); - check(TOKEN_STRING(js, tok[0], "strVar")); - check(TOKEN_STRING(js, tok[1], "")); - - return 0; -} - -int test_partial_string(void) { - int r; - jsmn_parser p; - jsmntok_t tok[10]; - const char *js; - - jsmn_init(&p); - js = "\"x\": \"va"; - r = jsmn_parse(&p, js, strlen(js), tok, 10); - check(r == JSMN_ERROR_PART && tok[0].type == JSMN_STRING); - check(TOKEN_STRING(js, tok[0], "x")); - check(p.toknext == 1); - - jsmn_init(&p); - js = "\"x\": \"va\\"; - r = jsmn_parse(&p, js, 9, tok, 10); - check(r == JSMN_ERROR_PART); - - jsmn_init(&p); - js = "\"x\": \"va\\u"; - r = jsmn_parse(&p, js, 10, tok, 10); - check(r == JSMN_ERROR_PART); - - js = "\"x\": \"valu"; - r = jsmn_parse(&p, js, strlen(js), tok, 10); - check(r == JSMN_ERROR_PART && tok[0].type == JSMN_STRING); - check(TOKEN_STRING(js, tok[0], "x")); - check(p.toknext == 1); - - js = "\"x\": \"value\""; - r = jsmn_parse(&p, js, strlen(js), tok, 10); - check(r >= 0 && tok[0].type == JSMN_STRING - && tok[1].type == JSMN_STRING); - check(TOKEN_STRING(js, tok[0], "x")); - check(TOKEN_STRING(js, tok[1], "value")); - - js = "\"x\": \"value\", \"y\": \"value y\""; - r = jsmn_parse(&p, js, strlen(js), tok, 10); - check(r >= 0 && tok[0].type == JSMN_STRING - && tok[1].type == JSMN_STRING && tok[2].type == JSMN_STRING - && tok[3].type == JSMN_STRING); - check(TOKEN_STRING(js, tok[0], "x")); - check(TOKEN_STRING(js, tok[1], "value")); - check(TOKEN_STRING(js, tok[2], "y")); - check(TOKEN_STRING(js, tok[3], "value y")); - - return 0; -} - -int test_unquoted_keys(void) { -#ifndef JSMN_STRICT - int r; - jsmn_parser p; - jsmntok_t tok[10]; - const char *js; - - jsmn_init(&p); - js = "key1: \"value\"\nkey2 : 123"; - - r = jsmn_parse(&p, js, strlen(js), tok, 10); - check(r >= 0 && tok[0].type == JSMN_PRIMITIVE - && tok[1].type == JSMN_STRING && tok[2].type == JSMN_PRIMITIVE - && tok[3].type == JSMN_PRIMITIVE); - check(TOKEN_STRING(js, tok[0], "key1")); - check(TOKEN_STRING(js, tok[1], "value")); - check(TOKEN_STRING(js, tok[2], "key2")); - check(TOKEN_STRING(js, tok[3], "123")); -#endif - return 0; -} - -int test_partial_array(void) { - int r; - jsmn_parser p; - jsmntok_t tok[10]; - const char *js; - - jsmn_init(&p); - js = " [ 1, true, "; - r = jsmn_parse(&p, js, strlen(js), tok, 10); - check(r == JSMN_ERROR_PART && tok[0].type == JSMN_ARRAY - && tok[1].type == JSMN_PRIMITIVE && tok[2].type == JSMN_PRIMITIVE); - - js = " [ 1, true, [123, \"hello"; - r = jsmn_parse(&p, js, strlen(js), tok, 10); - check(r == JSMN_ERROR_PART && tok[0].type == JSMN_ARRAY - && tok[1].type == JSMN_PRIMITIVE && tok[2].type == JSMN_PRIMITIVE - && tok[3].type == JSMN_ARRAY && tok[4].type == JSMN_PRIMITIVE); - - js = " [ 1, true, [123, \"hello\"]"; - r = jsmn_parse(&p, js, strlen(js), tok, 10); - check(r == JSMN_ERROR_PART && tok[0].type == JSMN_ARRAY - && tok[1].type == JSMN_PRIMITIVE && tok[2].type == JSMN_PRIMITIVE - && tok[3].type == JSMN_ARRAY && tok[4].type == JSMN_PRIMITIVE - && tok[5].type == JSMN_STRING); - /* check child nodes of the 2nd array */ - check(tok[3].size == 2); - - js = " [ 1, true, [123, \"hello\"]]"; - r = jsmn_parse(&p, js, strlen(js), tok, 10); - check(r >= 0 && tok[0].type == JSMN_ARRAY - && tok[1].type == JSMN_PRIMITIVE && tok[2].type == JSMN_PRIMITIVE - && tok[3].type == JSMN_ARRAY && tok[4].type == JSMN_PRIMITIVE - && tok[5].type == JSMN_STRING); - check(tok[3].size == 2); - check(tok[0].size == 3); - return 0; -} - -int test_array_nomem(void) { - int i; - int r; - jsmn_parser p; - jsmntok_t toksmall[10], toklarge[10]; - const char *js; - - js = " [ 1, true, [123, \"hello\"]]"; - - for (i = 0; i < 6; i++) { - jsmn_init(&p); - memset(toksmall, 0, sizeof(toksmall)); - memset(toklarge, 0, sizeof(toklarge)); - r = jsmn_parse(&p, js, strlen(js), toksmall, i); - check(r == JSMN_ERROR_NOMEM); - - memcpy(toklarge, toksmall, sizeof(toksmall)); - - r = jsmn_parse(&p, js, strlen(js), toklarge, 10); - check(r >= 0); - - check(toklarge[0].type == JSMN_ARRAY && toklarge[0].size == 3); - check(toklarge[3].type == JSMN_ARRAY && toklarge[3].size == 2); - } - return 0; -} - -int test_objects_arrays(void) { - int r; - jsmn_parser p; - jsmntok_t tokens[10]; - const char *js; - - js = "[10}"; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), tokens, 10); - check(r == JSMN_ERROR_INVAL); - - js = "[10]"; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), tokens, 10); - check(r >= 0); - - js = "{\"a\": 1]"; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), tokens, 10); - check(r == JSMN_ERROR_INVAL); - - js = "{\"a\": 1}"; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), tokens, 10); - check(r >= 0); - - return 0; -} - -int test_issue_22(void) { - int r; - jsmn_parser p; - jsmntok_t tokens[128]; - const char *js; - - js = "{ \"height\":10, \"layers\":[ { \"data\":[6,6], \"height\":10, " - "\"name\":\"Calque de Tile 1\", \"opacity\":1, \"type\":\"tilelayer\", " - "\"visible\":true, \"width\":10, \"x\":0, \"y\":0 }], " - "\"orientation\":\"orthogonal\", \"properties\": { }, \"tileheight\":32, " - "\"tilesets\":[ { \"firstgid\":1, \"image\":\"..\\/images\\/tiles.png\", " - "\"imageheight\":64, \"imagewidth\":160, \"margin\":0, \"name\":\"Tiles\", " - "\"properties\":{}, \"spacing\":0, \"tileheight\":32, \"tilewidth\":32 }], " - "\"tilewidth\":32, \"version\":1, \"width\":10 }"; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), tokens, 128); - check(r >= 0); -#if 0 - for (i = 1; tokens[i].end < tokens[0].end; i++) { - if (tokens[i].type == JSMN_STRING || tokens[i].type == JSMN_PRIMITIVE) { - printf("%.*s\n", tokens[i].end - tokens[i].start, js + tokens[i].start); - } else if (tokens[i].type == JSMN_ARRAY) { - printf("[%d elems]\n", tokens[i].size); - } else if (tokens[i].type == JSMN_OBJECT) { - printf("{%d elems}\n", tokens[i].size); - } else { - TOKEN_PRINT(tokens[i]); - } - } -#endif - return 0; -} - -int test_unicode_characters(void) { - jsmn_parser p; - jsmntok_t tokens[10]; - const char *js; - - int r; - js = "{\"a\":\"\\uAbcD\"}"; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), tokens, 10); - check(r >= 0); - - js = "{\"a\":\"str\\u0000\"}"; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), tokens, 10); - check(r >= 0); - - js = "{\"a\":\"\\uFFFFstr\"}"; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), tokens, 10); - check(r >= 0); - - js = "{\"a\":\"str\\uFFGFstr\"}"; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), tokens, 10); - check(r == JSMN_ERROR_INVAL); - - js = "{\"a\":\"str\\u@FfF\"}"; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), tokens, 10); - check(r == JSMN_ERROR_INVAL); - - js = "{\"a\":[\"\\u028\"]}"; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), tokens, 10); - check(r == JSMN_ERROR_INVAL); - - js = "{\"a\":[\"\\u0280\"]}"; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), tokens, 10); - check(r >= 0); - - return 0; -} - -int test_input_length(void) { - const char *js; - int r; - jsmn_parser p; - jsmntok_t tokens[10]; - - js = "{\"a\": 0}garbage"; - - jsmn_init(&p); - r = jsmn_parse(&p, js, 8, tokens, 10); - check(r == 3); - check(TOKEN_STRING(js, tokens[0], "{\"a\": 0}")); - check(TOKEN_STRING(js, tokens[1], "a")); - check(TOKEN_STRING(js, tokens[2], "0")); - - return 0; -} - -int test_count(void) { - jsmn_parser p; - const char *js; - - js = "{}"; - jsmn_init(&p); - check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 1); - - js = "[]"; - jsmn_init(&p); - check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 1); - - js = "[[]]"; - jsmn_init(&p); - check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 2); - - js = "[[], []]"; - jsmn_init(&p); - check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 3); - - js = "[[], []]"; - jsmn_init(&p); - check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 3); - - js = "[[], [[]], [[], []]]"; - jsmn_init(&p); - check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 7); - - js = "[\"a\", [[], []]]"; - jsmn_init(&p); - check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 5); - - js = "[[], \"[], [[]]\", [[]]]"; - jsmn_init(&p); - check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 5); - - js = "[1, 2, 3]"; - jsmn_init(&p); - check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 4); - - js = "[1, 2, [3, \"a\"], null]"; - jsmn_init(&p); - check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 7); - - return 0; -} - -int test_keyvalue(void) { - const char *js; - int r; - jsmn_parser p; - jsmntok_t tokens[10]; - - js = "{\"a\": 0, \"b\": \"c\"}"; - - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), tokens, 10); - check(r == 5); - check(tokens[0].size == 2); /* two keys */ - check(tokens[1].size == 1 && tokens[3].size == 1); /* one value per key */ - check(tokens[2].size == 0 && tokens[4].size == 0); /* values have zero size */ - - js = "{\"a\"\n0}"; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), tokens, 10); - check(r == JSMN_ERROR_INVAL); - - js = "{\"a\", 0}"; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), tokens, 10); - check(r == JSMN_ERROR_INVAL); - - js = "{\"a\": {2}}"; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), tokens, 10); - check(r == JSMN_ERROR_INVAL); - - js = "{\"a\": {2: 3}}"; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), tokens, 10); - check(r == JSMN_ERROR_INVAL); - - - js = "{\"a\": {\"a\": 2 3}}"; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), tokens, 10); - check(r == JSMN_ERROR_INVAL); - return 0; -} - -/** A huge redefinition of everything to include jsmn in non-script mode */ -#define jsmn_init jsmn_init_nonstrict -#define jsmn_parse jsmn_parse_nonstrict -#define jsmn_parser jsmn_parser_nonstrict -#define jsmn_alloc_token jsmn_alloc_token_nonstrict -#define jsmn_fill_token jsmn_fill_token_nonstrict -#define jsmn_parse_primitive jsmn_parse_primitive_nonstrict -#define jsmn_parse_string jsmn_parse_string_nonstrict -#define jsmntype_t jsmntype_nonstrict_t -#define jsmnerr_t jsmnerr_nonstrict_t -#define jsmntok_t jsmntok_nonstrict_t -#define JSMN_OBJECT JSMN_OBJECT_NONSTRICT -#define JSMN_ARRAY JSMN_ARRAY_NONSTRICT -#define JSMN_STRING JSMN_STRING_NONSTRICT -#define JSMN_PRIMITIVE JSMN_PRIMITIVE_NONSTRICT -#define JSMN_UNDEFINED JSMN_UNDEFINED_NONSTRICT -#define JSMN_ERROR_NOMEM JSMN_ERROR_NOMEM_NONSTRICT -#define JSMN_ERROR_INVAL JSMN_ERROR_INVAL_NONSTRICT -#define JSMN_ERROR_PART JSMN_ERROR_PART_NONSTRICT -#undef __JSMN_H_ -#undef JSMN_STRICT -#include "jsmn.c" - -int test_nonstrict(void) { - const char *js; - int r; - jsmn_parser p; - jsmntok_t tokens[10]; - - js = "a: 0garbage"; - - jsmn_init(&p); - r = jsmn_parse(&p, js, 4, tokens, 10); - check(r == 2); - check(TOKEN_STRING(js, tokens[0], "a")); - check(TOKEN_STRING(js, tokens[1], "0")); - - js = "Day : 26\nMonth : Sep\n\nYear: 12"; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), tokens, 10); - check(r == 6); - return 0; -} - -int main(void) { - test(test_empty, "general test for a empty JSON objects/arrays"); - test(test_simple, "general test for a simple JSON string"); - test(test_primitive, "test primitive JSON data types"); - test(test_string, "test string JSON data types"); - test(test_partial_string, "test partial JSON string parsing"); - test(test_partial_array, "test partial array reading"); - test(test_array_nomem, "test array reading with a smaller number of tokens"); - test(test_unquoted_keys, "test unquoted keys (like in JavaScript)"); - test(test_objects_arrays, "test objects and arrays"); - test(test_unicode_characters, "test unicode characters"); - test(test_input_length, "test strings that are not null-terminated"); - test(test_issue_22, "test issue #22"); - test(test_count, "test tokens count estimation"); - test(test_nonstrict, "test for non-strict mode"); - test(test_keyvalue, "test for keys/values"); - printf("\nPASSED: %d\nFAILED: %d\n", test_passed, test_failed); - return (test_failed > 0); -} - diff --git a/test/test.h b/test/test.h new file mode 100644 index 000000000..930ebaf92 --- /dev/null +++ b/test/test.h @@ -0,0 +1,27 @@ +#ifndef __TEST_H__ +#define __TEST_H__ + +static int test_passed = 0; +static int test_failed = 0; + +/* Terminate current test with error */ +#define fail() return __LINE__ + +/* Successfull end of the test case */ +#define done() return 0 + +/* Check single condition */ +#define check(cond) do { if (!(cond)) fail(); } while (0) + +/* Test runner */ +static void test(int (*func)(void), const char *name) { + int r = func(); + if (r == 0) { + test_passed++; + } else { + test_failed++; + printf("FAILED: %s (at line %d)\n", name, r); + } +} + +#endif /* __TEST_H__ */ diff --git a/test/tests.c b/test/tests.c new file mode 100644 index 000000000..4171f6f94 --- /dev/null +++ b/test/tests.c @@ -0,0 +1,362 @@ +#include +#include +#include +#include + +#include "test.h" +#include "testutil.h" + +int test_empty(void) { + check(parse("{}", 1, 1, + JSMN_OBJECT, 0, 2, 0)); + check(parse("[]", 1, 1, + JSMN_ARRAY, 0, 2, 0)); + check(parse("[{},{}]", 3, 3, + JSMN_ARRAY, 0, 7, 2, + JSMN_OBJECT, 1, 3, 0, + JSMN_OBJECT, 4, 6, 0)); + return 0; +} + +int test_object(void) { + check(parse("{\"a\":0}", 3, 3, + JSMN_OBJECT, 0, 7, 1, + JSMN_STRING, "a", 1, + JSMN_PRIMITIVE, "0")); + check(parse("{\"a\":[]}", 3, 3, + JSMN_OBJECT, 0, 8, 1, + JSMN_STRING, "a", 1, + JSMN_ARRAY, 5, 7, 0)); + check(parse("{\"a\":{},\"b\":{}}", 5, 5, + JSMN_OBJECT, -1, -1, 2, + JSMN_STRING, "a", 1, + JSMN_OBJECT, -1, -1, 0, + JSMN_STRING, "b", 1, + JSMN_OBJECT, -1, -1, 0)); + check(parse("{\n \"Day\": 26,\n \"Month\": 9,\n \"Year\": 12\n }", 7, 7, + JSMN_OBJECT, -1, -1, 3, + JSMN_STRING, "Day", 1, + JSMN_PRIMITIVE, "26", + JSMN_STRING, "Month", 1, + JSMN_PRIMITIVE, "9", + JSMN_STRING, "Year", 1, + JSMN_PRIMITIVE, "12")); + check(parse("{\"a\": 0, \"b\": \"c\"}", 5, 5, + JSMN_OBJECT, -1, -1, 2, + JSMN_STRING, "a", 1, + JSMN_PRIMITIVE, 0, + JSMN_STRING, "b", 1, + JSMN_STRING, "c", 0)); + +#ifdef JSMN_STRICT + check(parse("{\"a\"\n0}", JSMN_ERROR_INVAL, 3)); + check(parse("{\"a\", 0}", JSMN_ERROR_INVAL, 3)); + check(parse("{\"a\": {2}}", JSMN_ERROR_INVAL, 3)); + check(parse("{\"a\": {2: 3}}", JSMN_ERROR_INVAL, 3)); + check(parse("{\"a\": {\"a\": 2 3}}", JSMN_ERROR_INVAL, 5)); +#endif + return 0; +} + +int test_array(void) { + /* FIXME */ + /*check(parse("[10}", JSMN_ERROR_INVAL, 3));*/ + check(parse("[10]", 2, 2, + JSMN_ARRAY, -1, -1, 1, + JSMN_PRIMITIVE, "10")); + check(parse("{\"a\": 1]", JSMN_ERROR_INVAL, 3)); + /* FIXME */ + /*check(parse("[\"a\": 1]", JSMN_ERROR_INVAL, 3));*/ + return 0; +} + +int test_primitive(void) { + check(parse("{\"boolVar\" : true }", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "boolVar", 1, + JSMN_PRIMITIVE, "true")); + check(parse("{\"boolVar\" : false }", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "boolVar", 1, + JSMN_PRIMITIVE, "false")); + check(parse("{\"nullVar\" : null }", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "nullVar", 1, + JSMN_PRIMITIVE, "null")); + check(parse("{\"intVar\" : 12}", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "intVar", 1, + JSMN_PRIMITIVE, "12")); + check(parse("{\"floatVar\" : 12.345}", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "floatVar", 1, + JSMN_PRIMITIVE, "12.345")); + return 0; +} + +int test_string(void) { + check(parse("{\"strVar\" : \"hello world\"}", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "strVar", 1, + JSMN_STRING, "hello world", 0)); + check(parse("{\"strVar\" : \"escapes: \\/\\r\\n\\t\\b\\f\\\"\\\\\"}", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "strVar", 1, + JSMN_STRING, "escapes: \\/\\r\\n\\t\\b\\f\\\"\\\\", 0)); + check(parse("{\"strVar\": \"\"}", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "strVar", 1, + JSMN_STRING, "", 0)); + check(parse("{\"a\":\"\\uAbcD\"}", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "a", 1, + JSMN_STRING, "\\uAbcD", 0)); + check(parse("{\"a\":\"str\\u0000\"}", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "a", 1, + JSMN_STRING, "str\\u0000", 0)); + check(parse("{\"a\":\"\\uFFFFstr\"}", 3, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "a", 1, + JSMN_STRING, "\\uFFFFstr", 0)); + check(parse("{\"a\":[\"\\u0280\"]}", 4, 4, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "a", 1, + JSMN_ARRAY, -1, -1, 1, + JSMN_STRING, "\\u0280", 0)); + + check(parse("{\"a\":\"str\\uFFGFstr\"}", JSMN_ERROR_INVAL, 3)); + check(parse("{\"a\":\"str\\u@FfF\"}", JSMN_ERROR_INVAL, 3)); + check(parse("{{\"a\":[\"\\u028\"]}", JSMN_ERROR_INVAL, 4)); + return 0; +} + +int test_partial_string(void) { + int i; + int r; + jsmn_parser p; + jsmntok_t tok[5]; + const char *js = "{\"x\": \"va\\\\ue\", \"y\": \"value y\"}"; + + jsmn_init(&p); + for (i = 1; i <= strlen(js); i++) { + r = jsmn_parse(&p, js, i, tok, sizeof(tok)/sizeof(tok[0])); + if (i == strlen(js)) { + check(r == 5); + check(tokeq(js, tok, 5, + JSMN_OBJECT, -1, -1, 2, + JSMN_STRING, "x", 1, + JSMN_STRING, "va\\\\ue", 0, + JSMN_STRING, "y", 1, + JSMN_STRING, "value y", 0)); + } else { + check(r == JSMN_ERROR_PART); + } + } + return 0; +} + +int test_partial_array(void) { +#ifdef JSMN_STRICT + int r; + int i; + jsmn_parser p; + jsmntok_t tok[10]; + const char *js = "[ 1, true, [123, \"hello\"]]"; + + jsmn_init(&p); + for (i = 1; i <= strlen(js); i++) { + r = jsmn_parse(&p, js, i, tok, sizeof(tok)/sizeof(tok[0])); + if (i == strlen(js)) { + check(r == 6); + check(tokeq(js, tok, 6, + JSMN_ARRAY, -1, -1, 3, + JSMN_PRIMITIVE, "1", + JSMN_PRIMITIVE, "true", + JSMN_ARRAY, -1, -1, 2, + JSMN_PRIMITIVE, "123", + JSMN_STRING, "hello", 0)); + } else { + check(r == JSMN_ERROR_PART); + } + } +#endif + return 0; +} + +int test_array_nomem(void) { + int i; + int r; + jsmn_parser p; + jsmntok_t toksmall[10], toklarge[10]; + const char *js; + + js = " [ 1, true, [123, \"hello\"]]"; + + for (i = 0; i < 6; i++) { + jsmn_init(&p); + memset(toksmall, 0, sizeof(toksmall)); + memset(toklarge, 0, sizeof(toklarge)); + r = jsmn_parse(&p, js, strlen(js), toksmall, i); + check(r == JSMN_ERROR_NOMEM); + + memcpy(toklarge, toksmall, sizeof(toksmall)); + + r = jsmn_parse(&p, js, strlen(js), toklarge, 10); + check(r >= 0); + check(tokeq(js, toklarge, 4, + JSMN_ARRAY, -1, -1, 3, + JSMN_PRIMITIVE, "1", + JSMN_PRIMITIVE, "true", + JSMN_ARRAY, -1, -1, 2, + JSMN_PRIMITIVE, "123", + JSMN_STRING, "hello", 0)); + } + return 0; +} + +int test_unquoted_keys(void) { +#ifndef JSMN_STRICT + int r; + jsmn_parser p; + jsmntok_t tok[10]; + const char *js; + + jsmn_init(&p); + js = "key1: \"value\"\nkey2 : 123"; + + r = jsmn_parse(&p, js, strlen(js), tok, 10); + check(r >= 0); + check(tokeq(js, tok, 4, + JSMN_PRIMITIVE, "key1", + JSMN_STRING, "value", 0, + JSMN_PRIMITIVE, "key2", + JSMN_PRIMITIVE, "123")); +#endif + return 0; +} + +int test_issue_22(void) { + int r; + jsmn_parser p; + jsmntok_t tokens[128]; + const char *js; + + js = "{ \"height\":10, \"layers\":[ { \"data\":[6,6], \"height\":10, " + "\"name\":\"Calque de Tile 1\", \"opacity\":1, \"type\":\"tilelayer\", " + "\"visible\":true, \"width\":10, \"x\":0, \"y\":0 }], " + "\"orientation\":\"orthogonal\", \"properties\": { }, \"tileheight\":32, " + "\"tilesets\":[ { \"firstgid\":1, \"image\":\"..\\/images\\/tiles.png\", " + "\"imageheight\":64, \"imagewidth\":160, \"margin\":0, \"name\":\"Tiles\", " + "\"properties\":{}, \"spacing\":0, \"tileheight\":32, \"tilewidth\":32 }], " + "\"tilewidth\":32, \"version\":1, \"width\":10 }"; + jsmn_init(&p); + r = jsmn_parse(&p, js, strlen(js), tokens, 128); + check(r >= 0); + return 0; +} + +int test_input_length(void) { + const char *js; + int r; + jsmn_parser p; + jsmntok_t tokens[10]; + + js = "{\"a\": 0}garbage"; + + jsmn_init(&p); + r = jsmn_parse(&p, js, 8, tokens, 10); + check(r == 3); + check(tokeq(js, tokens, 3, + JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "a", 1, + JSMN_PRIMITIVE, 0)); + return 0; +} + +int test_count(void) { + jsmn_parser p; + const char *js; + + js = "{}"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 1); + + js = "[]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 1); + + js = "[[]]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 2); + + js = "[[], []]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 3); + + js = "[[], []]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 3); + + js = "[[], [[]], [[], []]]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 7); + + js = "[\"a\", [[], []]]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 5); + + js = "[[], \"[], [[]]\", [[]]]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 5); + + js = "[1, 2, 3]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 4); + + js = "[1, 2, [3, \"a\"], null]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 7); + + return 0; +} + + +int test_nonstrict(void) { +#ifndef JSMN_STRICT + const char *js; + js = "a: 0garbage"; + check(parse(js, 2, 2, + JSMN_PRIMITIVE, "a", + JSMN_PRIMITIVE, "0garbage")); + + js = "Day : 26\nMonth : Sep\n\nYear: 12"; + check(parse(js, 6, 6, + JSMN_PRIMITIVE, "Day", + JSMN_PRIMITIVE, "26", + JSMN_PRIMITIVE, "Month", + JSMN_PRIMITIVE, "Sep", + JSMN_PRIMITIVE, "Year", + JSMN_PRIMITIVE, "12")); +#endif + return 0; +} + +int main(void) { + test(test_empty, "test for a empty JSON objects/arrays"); + test(test_object, "test for a JSON objects"); + test(test_array, "test for a JSON arrays"); + test(test_primitive, "test primitive JSON data types"); + test(test_string, "test string JSON data types"); + + test(test_partial_string, "test partial JSON string parsing"); + test(test_partial_array, "test partial array reading"); + test(test_array_nomem, "test array reading with a smaller number of tokens"); + test(test_unquoted_keys, "test unquoted keys (like in JavaScript)"); + test(test_input_length, "test strings that are not null-terminated"); + test(test_issue_22, "test issue #22"); + test(test_count, "test tokens count estimation"); + test(test_nonstrict, "test for non-strict mode"); + printf("\nPASSED: %d\nFAILED: %d\n", test_passed, test_failed); + return (test_failed > 0); +} diff --git a/test/testutil.h b/test/testutil.h new file mode 100644 index 000000000..dea8846fd --- /dev/null +++ b/test/testutil.h @@ -0,0 +1,94 @@ +#ifndef __TEST_UTIL_H__ +#define __TEST_UTIL_H__ + +#include "../jsmn.c" + +static int vtokeq(const char *s, jsmntok_t *t, int numtok, va_list ap) { + if (numtok > 0) { + int i, start, end, size; + int type; + char *value; + + size = -1; + value = NULL; + for (i = 0; i < numtok; i++) { + type = va_arg(ap, int); + if (type == JSMN_STRING) { + value = va_arg(ap, char *); + size = va_arg(ap, int); + start = end = -1; + } else if (type == JSMN_PRIMITIVE) { + value = va_arg(ap, char *); + start = end = size = -1; + } else { + start = va_arg(ap, int); + end = va_arg(ap, int); + size = va_arg(ap, int); + value = NULL; + } + if (t[i].type != type) { + printf("token %d type is %d, not %d\n", i, t[i].type, type); + return 0; + } + if (start != -1 && end != -1) { + if (t[i].start != start) { + printf("token %d start is %d, not %d\n", i, t[i].start, start); + return 0; + } + if (t[i].end != end ) { + printf("token %d end is %d, not %d\n", i, t[i].end, end); + return 0; + } + } + if (size != -1 && t[i].size != size) { + printf("token %d size is %d, not %d\n", i, t[i].size, size); + return 0; + } + + if (s != NULL && value != NULL) { + const char *p = s + t[i].start; + if (strlen(value) != t[i].end - t[i].start || + strncmp(p, value, t[i].end - t[i].start) != 0) { + printf("token %d value is %.*s, not %s\n", i, t[i].end-t[i].start, + s+t[i].start, value); + return 0; + } + } + } + } + return 1; +} + +static int tokeq(const char *s, jsmntok_t *tokens, int numtok, ...) { + int ok; + va_list args; + va_start(args, numtok); + ok = vtokeq(s, tokens, numtok, args); + va_end(args); + return ok; +} + +static int parse(const char *s, int status, int numtok, ...) { + int r; + int ok; + va_list args; + jsmn_parser p; + jsmntok_t *t = malloc(numtok * sizeof(jsmntok_t)); + + jsmn_init(&p); + r = jsmn_parse(&p, s, strlen(s), t, numtok); + if (r != status) { + printf("status is %d, not %d\n", r, status); + return 0; + } + + if (status >= 0) { + va_start(args, numtok); + ok = vtokeq(s, t, numtok, args); + va_end(args); + } + free(t); + return ok; +} + +#endif /* __TEST_UTIL_H__ */ -- cgit v1.2.3 From edd751896de5de1fcee95cb65949e6784c726751 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Sat, 17 Oct 2015 14:58:47 +0200 Subject: fixed return value on incremental parting --- jsmn.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsmn.c b/jsmn.c index a0f4f69c6..7d701a724 100644 --- a/jsmn.c +++ b/jsmn.c @@ -155,7 +155,7 @@ jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t len, jsmnerr_t r; int i; jsmntok_t *token; - int count = 0; + int count = parser->toknext; for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { char c; -- cgit v1.2.3 From 5e37daeec3cb49dc34a9377d3d2230c0394d1f3c Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Sat, 17 Oct 2015 15:01:01 +0200 Subject: added issue 27 test case --- test/tests.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/tests.c b/test/tests.c index 4171f6f94..4563d3985 100644 --- a/test/tests.c +++ b/test/tests.c @@ -256,6 +256,13 @@ int test_issue_22(void) { return 0; } +int test_issue_27(void) { + const char *js = + "{ \"name\" : \"Jack\", \"age\" : 27 } { \"name\" : \"Anna\", "; + check(parse(js, JSMN_ERROR_PART, 8)); + return 0; +} + int test_input_length(void) { const char *js; int r; @@ -355,6 +362,7 @@ int main(void) { test(test_unquoted_keys, "test unquoted keys (like in JavaScript)"); test(test_input_length, "test strings that are not null-terminated"); test(test_issue_22, "test issue #22"); + test(test_issue_27, "test issue #22"); test(test_count, "test tokens count estimation"); test(test_nonstrict, "test for non-strict mode"); printf("\nPASSED: %d\nFAILED: %d\n", test_passed, test_failed); -- cgit v1.2.3 From d0664f9a2ee942e0edaa255309f67b3f77e04b50 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Sat, 17 Oct 2015 15:06:39 +0200 Subject: changed jsmnerr_t type to int --- jsmn.c | 8 ++++---- jsmn.h | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/jsmn.c b/jsmn.c index 7d701a724..0e3ad740f 100644 --- a/jsmn.c +++ b/jsmn.c @@ -34,7 +34,7 @@ static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, /** * Fills next available token with JSON primitive. */ -static jsmnerr_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, +static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, size_t num_tokens) { jsmntok_t *token; int start; @@ -83,7 +83,7 @@ found: /** * Filsl next token with JSON string. */ -static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, +static int jsmn_parse_string(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, size_t num_tokens) { jsmntok_t *token; @@ -150,9 +150,9 @@ static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, /** * Parse JSON string and fill tokens. */ -jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t len, +int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, unsigned int num_tokens) { - jsmnerr_t r; + int r; int i; jsmntok_t *token; int count = parser->toknext; diff --git a/jsmn.h b/jsmn.h index 59cbaa2be..01ca99c8e 100644 --- a/jsmn.h +++ b/jsmn.h @@ -22,14 +22,14 @@ typedef enum { JSMN_PRIMITIVE = 4 } jsmntype_t; -typedef enum { +enum jsmnerr { /* Not enough tokens were provided */ JSMN_ERROR_NOMEM = -1, /* Invalid character inside JSON string */ JSMN_ERROR_INVAL = -2, /* The string is not a full JSON packet, more bytes expected */ JSMN_ERROR_PART = -3 -} jsmnerr_t; +}; /** * JSON token description. @@ -66,7 +66,7 @@ void jsmn_init(jsmn_parser *parser); * Run JSON parser. It parses a JSON data string into and array of tokens, each describing * a single JSON object. */ -jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t len, +int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, unsigned int num_tokens); #ifdef __cplusplus -- cgit v1.2.3 From 824d9a769c38584c5aef9f6f12642645c66dc055 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Sat, 17 Oct 2015 15:09:35 +0200 Subject: applied fix from wireshark --- jsmn.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jsmn.c b/jsmn.c index 0e3ad740f..74a62d075 100644 --- a/jsmn.c +++ b/jsmn.c @@ -240,7 +240,7 @@ int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, parser->toksuper = parser->toknext - 1; break; case ',': - if (tokens != NULL && + if (tokens != NULL && parser->toksuper != -1 && tokens[parser->toksuper].type != JSMN_ARRAY && tokens[parser->toksuper].type != JSMN_OBJECT) { #ifdef JSMN_PARENT_LINKS @@ -263,7 +263,7 @@ int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, case '5': case '6': case '7' : case '8': case '9': case 't': case 'f': case 'n' : /* And they must not be keys of the object */ - if (tokens != NULL) { + if (tokens != NULL && parser->toksuper != -1) { jsmntok_t *t = &tokens[parser->toksuper]; if (t->type == JSMN_OBJECT || (t->type == JSMN_STRING && t->size != 0)) { -- cgit v1.2.3 From aedc121ce8a5ef0156ee6cb7e06fcd96457220a0 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Sat, 17 Oct 2015 15:14:13 +0200 Subject: added check for null pointer before fixing tokens after parsing is done --- jsmn.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/jsmn.c b/jsmn.c index 74a62d075..2809669b3 100644 --- a/jsmn.c +++ b/jsmn.c @@ -289,10 +289,12 @@ int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, } } - for (i = parser->toknext - 1; i >= 0; i--) { - /* Unmatched opened object or array */ - if (tokens[i].start != -1 && tokens[i].end == -1) { - return JSMN_ERROR_PART; + if (tokens != NULL) { + for (i = parser->toknext - 1; i >= 0; i--) { + /* Unmatched opened object or array */ + if (tokens[i].start != -1 && tokens[i].end == -1) { + return JSMN_ERROR_PART; + } } } -- cgit v1.2.3 From e5fb875b593447c33accaf3572c5b7bc662829d7 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Sat, 17 Oct 2015 15:19:01 +0200 Subject: added another test of invalid array --- test/tests.c | 1 + 1 file changed, 1 insertion(+) diff --git a/test/tests.c b/test/tests.c index 4563d3985..b52a13c5d 100644 --- a/test/tests.c +++ b/test/tests.c @@ -61,6 +61,7 @@ int test_object(void) { int test_array(void) { /* FIXME */ /*check(parse("[10}", JSMN_ERROR_INVAL, 3));*/ + /*check(parse("[1,,3]", JSMN_ERROR_INVAL, 3)*/ check(parse("[10]", 2, 2, JSMN_ARRAY, -1, -1, 1, JSMN_PRIMITIVE, "10")); -- cgit v1.2.3 From f7e6dcb7a2296386f251417e3fc259ef43abb41c Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Sat, 17 Oct 2015 15:24:44 +0200 Subject: added another test of invalid array --- test/tests.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/tests.c b/test/tests.c index b52a13c5d..4e4cc13fa 100644 --- a/test/tests.c +++ b/test/tests.c @@ -54,6 +54,12 @@ int test_object(void) { check(parse("{\"a\": {2}}", JSMN_ERROR_INVAL, 3)); check(parse("{\"a\": {2: 3}}", JSMN_ERROR_INVAL, 3)); check(parse("{\"a\": {\"a\": 2 3}}", JSMN_ERROR_INVAL, 5)); + check(parse("{\"a\"}", JSMN_ERROR_INVAL, 2)); + check(parse("{\"a\": 1, \"b\"}", JSMN_ERROR_INVAL, 4)); + check(parse("{\"a\",\"b\":1}", JSMN_ERROR_INVAL, 4)); + check(parse("{\"a\":1,}", JSMN_ERROR_INVAL, 4)); + check(parse("{\"a\":\"b\":\"c\"}", JSMN_ERROR_INVAL, 4)); + check(parse("{,}", JSMN_ERROR_INVAL, 4)); #endif return 0; } -- cgit v1.2.3 From 0c2d60b8e7820857fe14dfd113a90c94b2f18611 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Sat, 17 Oct 2015 15:25:44 +0200 Subject: added and marked as fixme tests for false positives in objects --- test/tests.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/test/tests.c b/test/tests.c index 4e4cc13fa..7d4d30e26 100644 --- a/test/tests.c +++ b/test/tests.c @@ -54,12 +54,13 @@ int test_object(void) { check(parse("{\"a\": {2}}", JSMN_ERROR_INVAL, 3)); check(parse("{\"a\": {2: 3}}", JSMN_ERROR_INVAL, 3)); check(parse("{\"a\": {\"a\": 2 3}}", JSMN_ERROR_INVAL, 5)); - check(parse("{\"a\"}", JSMN_ERROR_INVAL, 2)); - check(parse("{\"a\": 1, \"b\"}", JSMN_ERROR_INVAL, 4)); - check(parse("{\"a\",\"b\":1}", JSMN_ERROR_INVAL, 4)); - check(parse("{\"a\":1,}", JSMN_ERROR_INVAL, 4)); - check(parse("{\"a\":\"b\":\"c\"}", JSMN_ERROR_INVAL, 4)); - check(parse("{,}", JSMN_ERROR_INVAL, 4)); + /* FIXME */ + /*check(parse("{\"a\"}", JSMN_ERROR_INVAL, 2));*/ + /*check(parse("{\"a\": 1, \"b\"}", JSMN_ERROR_INVAL, 4));*/ + /*check(parse("{\"a\",\"b\":1}", JSMN_ERROR_INVAL, 4));*/ + /*check(parse("{\"a\":1,}", JSMN_ERROR_INVAL, 4));*/ + /*check(parse("{\"a\":\"b\":\"c\"}", JSMN_ERROR_INVAL, 4));*/ + /*check(parse("{,}", JSMN_ERROR_INVAL, 4));*/ #endif return 0; } -- cgit v1.2.3 From e709651a20f3b32c5cca9e3ca29c6b0b43bbb4d8 Mon Sep 17 00:00:00 2001 From: goriy Date: Sat, 24 Oct 2015 23:32:51 +0300 Subject: small fix of jsondump example for cases of realloc failures When realloc() function fails it returns NULL pointer. But old data pointer remains valid in such a case. It's a mistake to use old data pointer to store new pointer returned by realloc. In case of realloc failure, pointer is overwritten with NULL value, but old used memory remains unreferenced and could not be even freed anymore. Such mistakes could lead to memory leaks. --- example/jsondump.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/example/jsondump.c b/example/jsondump.c index 3490bbf49..a5b597252 100644 --- a/example/jsondump.c +++ b/example/jsondump.c @@ -48,7 +48,7 @@ static int dump(const char *js, jsmntok_t *t, size_t count, int indent) { int main() { int r; int eof_expected = 0; - char *js = NULL; + char *tmp, *js = NULL; size_t jslen = 0; char buf[BUFSIZ]; @@ -82,11 +82,13 @@ int main() { } } - js = realloc(js, jslen + r + 1); - if (js == NULL) { + tmp = realloc(js, jslen + r + 1); + if (tmp == NULL) { + free (js); fprintf(stderr, "realloc(): errno=%d\n", errno); return 3; } + js = tmp; strncpy(js + jslen, buf, r); jslen = jslen + r; @@ -94,12 +96,16 @@ again: r = jsmn_parse(&p, js, jslen, tok, tokcount); if (r < 0) { if (r == JSMN_ERROR_NOMEM) { + jsmntok_t *tmptok; + tokcount = tokcount * 2; - tok = realloc(tok, sizeof(*tok) * tokcount); - if (tok == NULL) { + tmptok = realloc(tok, sizeof(*tok) * tokcount); + if (tmptok == NULL) { + free (tok); fprintf(stderr, "realloc(): errno=%d\n", errno); return 3; } + tok = tmptok; goto again; } } else { -- cgit v1.2.3 From 2d185aa465782ba30bfaea5ccd39cea4917e69a8 Mon Sep 17 00:00:00 2001 From: goriy Date: Sun, 25 Oct 2015 23:28:19 +0300 Subject: tiny realloc function wrapper which handles cases of realloc failures Memory reallocation with old data pointer handling is done by means of realloc_it() function. Tiny inline function realloc_it() is a wrapper function for standart realloc() which frees old memory pointer and prints errno to stderr if realloc fails. --- example/jsondump.c | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/example/jsondump.c b/example/jsondump.c index a5b597252..7be628772 100644 --- a/example/jsondump.c +++ b/example/jsondump.c @@ -4,6 +4,21 @@ #include #include "../jsmn.h" +/* Function realloc_it() is a wrapper function for standart realloc() + * with one difference - it frees old memory pointer in case of realloc + * failure. Thus, DO NOT use old data pointer in anyway after call to + * realloc_it(). If your code has some kind of fallback algorithm if + * memory can't be re-allocated - use standart realloc() instead. + */ +static inline void *realloc_it(void *ptrmem, size_t size) { + void *p = realloc(ptrmem, size); + if (!p) { + free (ptrmem); + fprintf(stderr, "realloc(): errno=%d\n", errno); + } + return p; +} + /* * An example of reading JSON from stdin and printing its content to stdout. * The output looks like YAML, but I'm not sure if it's really compatible. @@ -48,7 +63,7 @@ static int dump(const char *js, jsmntok_t *t, size_t count, int indent) { int main() { int r; int eof_expected = 0; - char *tmp, *js = NULL; + char *js = NULL; size_t jslen = 0; char buf[BUFSIZ]; @@ -82,13 +97,10 @@ int main() { } } - tmp = realloc(js, jslen + r + 1); - if (tmp == NULL) { - free (js); - fprintf(stderr, "realloc(): errno=%d\n", errno); + js = realloc_it(js, jslen + r + 1); + if (js == NULL) { return 3; } - js = tmp; strncpy(js + jslen, buf, r); jslen = jslen + r; @@ -96,16 +108,11 @@ again: r = jsmn_parse(&p, js, jslen, tok, tokcount); if (r < 0) { if (r == JSMN_ERROR_NOMEM) { - jsmntok_t *tmptok; - tokcount = tokcount * 2; - tmptok = realloc(tok, sizeof(*tok) * tokcount); - if (tmptok == NULL) { - free (tok); - fprintf(stderr, "realloc(): errno=%d\n", errno); + tok = realloc_it(tok, sizeof(*tok) * tokcount); + if (tok == NULL) { return 3; } - tok = tmptok; goto again; } } else { -- cgit v1.2.3 From 8a5ee3d41b17a600effe0c994beac093ca836118 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Sat, 31 Oct 2015 18:33:43 +0200 Subject: fixed a typo in the test name --- test/tests.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tests.c b/test/tests.c index 7d4d30e26..982d79484 100644 --- a/test/tests.c +++ b/test/tests.c @@ -370,7 +370,7 @@ int main(void) { test(test_unquoted_keys, "test unquoted keys (like in JavaScript)"); test(test_input_length, "test strings that are not null-terminated"); test(test_issue_22, "test issue #22"); - test(test_issue_27, "test issue #22"); + test(test_issue_27, "test issue #27"); test(test_count, "test tokens count estimation"); test(test_nonstrict, "test for non-strict mode"); printf("\nPASSED: %d\nFAILED: %d\n", test_passed, test_failed); -- cgit v1.2.3 From 7fb89f1eb23a2a62d54fd5c3b9c44243828ecfd4 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Sat, 31 Oct 2015 18:37:25 +0200 Subject: initialized ok variable in tests, parse should still return 1 if expected result is a failure --- test/testutil.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/testutil.h b/test/testutil.h index dea8846fd..9a1eb2d65 100644 --- a/test/testutil.h +++ b/test/testutil.h @@ -70,7 +70,7 @@ static int tokeq(const char *s, jsmntok_t *tokens, int numtok, ...) { static int parse(const char *s, int status, int numtok, ...) { int r; - int ok; + int ok = 1; va_list args; jsmn_parser p; jsmntok_t *t = malloc(numtok * sizeof(jsmntok_t)); -- cgit v1.2.3 From 076abddc19f844b98e5ee591eae8d8a9a34c2c19 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Sat, 31 Oct 2015 18:43:04 +0200 Subject: fixed privitive value typo in tests --- test/tests.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tests.c b/test/tests.c index 982d79484..442074615 100644 --- a/test/tests.c +++ b/test/tests.c @@ -285,7 +285,7 @@ int test_input_length(void) { check(tokeq(js, tokens, 3, JSMN_OBJECT, -1, -1, 1, JSMN_STRING, "a", 1, - JSMN_PRIMITIVE, 0)); + JSMN_PRIMITIVE, "0")); return 0; } -- cgit v1.2.3 From f06a10c1b7b58784d20d2b06bf24b78cacf3fb83 Mon Sep 17 00:00:00 2001 From: Bastien Dejean Date: Thu, 19 Nov 2015 09:55:46 +0100 Subject: Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c7837f752..05e55874f 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,7 @@ All job is done by `jsmn_parser` object. You can initialize a new parser using: This will create a parser, and then it tries to parse up to 10 JSON tokens from the `js` string. -A non-negative reutrn value of `jsmn_parse` is the number of tokens actually +A non-negative return value of `jsmn_parse` is the number of tokens actually used by the parser. Passing NULL instead of the tokens array would not store parsing results, but instead the function will return the value of tokens needed to parse the given -- cgit v1.2.3 From 6bfa20872c82bab2e1f7824348081b2e9cd4d4b8 Mon Sep 17 00:00:00 2001 From: Eric Le Lay Date: Mon, 7 Dec 2015 20:14:07 +0100 Subject: fix jsmntype_t definition in README --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 05e55874f..d4fb404f7 100644 --- a/README.md +++ b/README.md @@ -97,10 +97,11 @@ API Token types are described by `jsmntype_t`: typedef enum { - JSMN_PRIMITIVE = 0, + JSMN_UNDEFINED = 0, JSMN_OBJECT = 1, JSMN_ARRAY = 2, - JSMN_STRING = 3 + JSMN_STRING = 3, + JSMN_PRIMITIVE = 4 } jsmntype_t; **Note:** Unlike JSON data types, primitive tokens are not divided into -- cgit v1.2.3 From e4d526a4033c4548dc3f9ade2ec14ff87b9455ea Mon Sep 17 00:00:00 2001 From: Bastien Dejean Date: Mon, 14 Dec 2015 15:05:46 +0100 Subject: Fix deheader warnings Prior to this commit the output of deheader was: remove from ./jsmn.c in ./example/jsondump.c, realloc() portability requires . in ./example/simple.c, strtol() portability requires . saw 4 files, 16 includes, 1 removable --- example/jsondump.c | 1 + example/simple.c | 1 + jsmn.c | 2 -- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/example/jsondump.c b/example/jsondump.c index 7be628772..cf08c5ca5 100644 --- a/example/jsondump.c +++ b/example/jsondump.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include "../jsmn.h" diff --git a/example/simple.c b/example/simple.c index a6f8e6a98..de4488383 100644 --- a/example/simple.c +++ b/example/simple.c @@ -1,4 +1,5 @@ #include +#include #include #include "../jsmn.h" diff --git a/jsmn.c b/jsmn.c index 2809669b3..bbf013de4 100644 --- a/jsmn.c +++ b/jsmn.c @@ -1,5 +1,3 @@ -#include - #include "jsmn.h" /** -- cgit v1.2.3 From b7845b4ea43b71829e802982235c9988960a589d Mon Sep 17 00:00:00 2001 From: Matthew Fernandez Date: Wed, 13 Jan 2016 21:45:00 +1100 Subject: Fix trivial comment typo. --- jsmn.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsmn.c b/jsmn.c index bbf013de4..e7765eb1d 100644 --- a/jsmn.c +++ b/jsmn.c @@ -79,7 +79,7 @@ found: } /** - * Filsl next token with JSON string. + * Fills next token with JSON string. */ static int jsmn_parse_string(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, size_t num_tokens) { -- cgit v1.2.3 From 86d595729cd0e1d2dd82f3fa1da6443c6b212c59 Mon Sep 17 00:00:00 2001 From: Ivan Kravets Date: Tue, 19 Jan 2016 16:34:12 +0200 Subject: @PlatformIO Library Registry manifest file * This library in Web Registry: http://platformio.org/#!/lib/show/568/jsmn * Specification: [library.json](http://docs.platformio.org/en/latest/librarymanager/config.html) * Library Manager: http://docs.platformio.org/en/latest/librarymanager/index.html --- library.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 library.json diff --git a/library.json b/library.json new file mode 100644 index 000000000..8e2f5c257 --- /dev/null +++ b/library.json @@ -0,0 +1,16 @@ +{ + "name": "jsmn", + "keywords": "json", + "description": "Minimalistic JSON parser/tokenizer in C. It can be easily integrated into resource-limited or embedded projects", + "repository": + { + "type": "git", + "url": "https://github.com/zserge/jsmn.git" + }, + "frameworks": "*", + "platforms": "*", + "examples": [ + "example/*.c" + ], + "exclude": "test" +} -- cgit v1.2.3 From 572ace5a43c43b1c6dc55f31fab03718faf2f647 Mon Sep 17 00:00:00 2001 From: Serge Zaitsev Date: Wed, 20 Jan 2016 17:28:15 +0800 Subject: Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d4fb404f7..6f6fb471b 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ All job is done by `jsmn_parser` object. You can initialize a new parser using: // js - pointer to JSON string // tokens - an array of tokens available // 10 - number of tokens available - jsmn_parse(&parser, js, tokens, 10); + jsmn_parse(&parser, js, strlen(js), tokens, 10); This will create a parser, and then it tries to parse up to 10 JSON tokens from the `js` string. -- cgit v1.2.3 From 76c9448ca8aceb7edeff63506708b9d272440a05 Mon Sep 17 00:00:00 2001 From: condemned77 Date: Mon, 28 Mar 2016 12:28:36 +0200 Subject: Typo fix. Minor typo fixed. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6f6fb471b..105897b8c 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ JSON format itself is extremely simple, so why should we complicate it? jsmn is designed to be **robust** (it should work fine even with erroneous data), **fast** (it should parse data on the fly), **portable** (no superfluous -dependencies or non-standard C extensions). An of course, **simplicity** is a +dependencies or non-standard C extensions). And of course, **simplicity** is a key feature - simple code style, simple algorithm, simple integration into other projects. -- cgit v1.2.3 From e3f2629a562c260fc3fe3d69576cd7b06f3a3760 Mon Sep 17 00:00:00 2001 From: Jon Simons Date: Fri, 1 Apr 2016 19:59:14 -0700 Subject: tests: fix test_object JSMN_PRIMITIVE bug Specify the argument after JSMN_PRIMITIVE as "0" instead of 0 so that weird things don't happen on OSX due to using it later with `va_arg(..., char *)` in `vtokeq`. --- test/tests.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tests.c b/test/tests.c index 442074615..a72689ec0 100644 --- a/test/tests.c +++ b/test/tests.c @@ -44,7 +44,7 @@ int test_object(void) { check(parse("{\"a\": 0, \"b\": \"c\"}", 5, 5, JSMN_OBJECT, -1, -1, 2, JSMN_STRING, "a", 1, - JSMN_PRIMITIVE, 0, + JSMN_PRIMITIVE, "0", JSMN_STRING, "b", 1, JSMN_STRING, "c", 0)); -- cgit v1.2.3 From 37672b0289b076de40b888042e629ed794663ee9 Mon Sep 17 00:00:00 2001 From: Feram Date: Mon, 13 Jun 2016 06:10:46 +0000 Subject: Fix typos --- test/test.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test.h b/test/test.h index 930ebaf92..35f704f99 100644 --- a/test/test.h +++ b/test/test.h @@ -7,7 +7,7 @@ static int test_failed = 0; /* Terminate current test with error */ #define fail() return __LINE__ -/* Successfull end of the test case */ +/* Successful end of the test case */ #define done() return 0 /* Check single condition */ -- cgit v1.2.3 From e42bcbbada01199e00be7576fd2fda69f04b8bd7 Mon Sep 17 00:00:00 2001 From: "Nicola Spanti (RyDroid)" Date: Mon, 8 Aug 2016 15:54:56 +0200 Subject: Very minor changes to C source code --- example/jsondump.c | 2 +- example/simple.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/example/jsondump.c b/example/jsondump.c index cf08c5ca5..4fb10fd51 100644 --- a/example/jsondump.c +++ b/example/jsondump.c @@ -122,5 +122,5 @@ again: } } - return 0; + return EXIT_SUCCESS; } diff --git a/example/simple.c b/example/simple.c index de4488383..aeb9cf47d 100644 --- a/example/simple.c +++ b/example/simple.c @@ -8,7 +8,7 @@ * tokens is predictable. */ -const char *JSON_STRING = +static const char *JSON_STRING = "{\"user\": \"johndoe\", \"admin\": false, \"uid\": 1000,\n " "\"groups\": [\"users\", \"wheel\", \"audio\", \"video\"]}"; @@ -72,5 +72,5 @@ int main() { JSON_STRING + t[i].start); } } - return 0; + return EXIT_SUCCESS; } -- cgit v1.2.3 From b80578ce0893f67e60c5daffa9b5d21a7c978cef Mon Sep 17 00:00:00 2001 From: "Nicola Spanti (RyDroid)" Date: Mon, 8 Aug 2016 16:05:12 +0200 Subject: Very minor changes to Makefile --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index a382d64d8..f89701fdd 100644 --- a/Makefile +++ b/Makefile @@ -32,8 +32,8 @@ jsondump: example/jsondump.o libjsmn.a $(CC) $(LDFLAGS) $^ -o $@ clean: - rm -f jsmn.o jsmn_test.o example/simple.o - rm -f libjsmn.a + rm -f *.o example/*.o + rm -f *.a *.so rm -f simple_example rm -f jsondump -- cgit v1.2.3 From ad72aac67ab84280cbd7e08b2668ef7fe5db046e Mon Sep 17 00:00:00 2001 From: pt300 Date: Sat, 1 Oct 2016 18:07:35 +0200 Subject: Partialy fixes zserge/jsmn#81 Still will report invalid amount if we fetch it with something like "{"key 1": 1234}}" --- jsmn.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jsmn.c b/jsmn.c index e7765eb1d..da9bf217c 100644 --- a/jsmn.c +++ b/jsmn.c @@ -198,6 +198,9 @@ int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, break; } if (token->parent == -1) { + if(token->type != type) { + return JSMN_ERROR_INVAL; + } break; } token = &tokens[token->parent]; -- cgit v1.2.3 From 4ce44040579e65755d14a3d7dabdae0913959a24 Mon Sep 17 00:00:00 2001 From: pt300 Date: Sat, 1 Oct 2016 18:19:20 +0200 Subject: Seems to actually fix zserge/jsmn#81 --- jsmn.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsmn.c b/jsmn.c index da9bf217c..bcd6392a0 100644 --- a/jsmn.c +++ b/jsmn.c @@ -198,7 +198,7 @@ int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, break; } if (token->parent == -1) { - if(token->type != type) { + if(token->type != type || parser->toksuper == -1) { return JSMN_ERROR_INVAL; } break; -- cgit v1.2.3 From a01d301373595892e2e5ff77dd4e7715f7897f11 Mon Sep 17 00:00:00 2001 From: zlolik Date: Sun, 2 Oct 2016 07:51:52 +0300 Subject: some tests for unmatched brackets added --- test/tests.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/tests.c b/test/tests.c index a72689ec0..424703522 100644 --- a/test/tests.c +++ b/test/tests.c @@ -357,6 +357,27 @@ int test_nonstrict(void) { return 0; } +int test_unmatched_brackets(void) { + const char *js; + js = "\"key 1\": 1234}"; + check(parse(js, JSMN_ERROR_INVAL, 2)); + js = "{\"key 1\": 1234"; + check(parse(js, JSMN_ERROR_PART, 3)); + js = "{\"key 1\": 1234}}"; + check(parse(js, JSMN_ERROR_INVAL, 3)); + js = "\"key 1\"}: 1234"; + check(parse(js, JSMN_ERROR_INVAL, 3)); + js = "\"key {1\": 1234"; + check(parse(js, 2, 2, + JSMN_PRIMITIVE, "\"key {1\"", + JSMN_PRIMITIVE, "1234")); + js = "\"key 1\": {1234}"; + check(parse(js, JSMN_ERROR_INVAL, 3)); + js = "{\"key 1\"}: 1234"; + check(parse(js, JSMN_ERROR_INVAL, 3)); + return 0; +} + int main(void) { test(test_empty, "test for a empty JSON objects/arrays"); test(test_object, "test for a JSON objects"); @@ -373,6 +394,7 @@ int main(void) { test(test_issue_27, "test issue #27"); test(test_count, "test tokens count estimation"); test(test_nonstrict, "test for non-strict mode"); + test(test_unmatched_brackets, "test for unmatched brackets"); printf("\nPASSED: %d\nFAILED: %d\n", test_passed, test_failed); return (test_failed > 0); } -- cgit v1.2.3 From c3131d05a6db72c1ddefb1ad4c95ddbc604d1fa6 Mon Sep 17 00:00:00 2001 From: pt300 Date: Sun, 2 Oct 2016 10:37:29 +0200 Subject: Changed unmatched bracket tests --- test/tests.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test/tests.c b/test/tests.c index 424703522..d5f0c5300 100644 --- a/test/tests.c +++ b/test/tests.c @@ -369,12 +369,10 @@ int test_unmatched_brackets(void) { check(parse(js, JSMN_ERROR_INVAL, 3)); js = "\"key {1\": 1234"; check(parse(js, 2, 2, - JSMN_PRIMITIVE, "\"key {1\"", + JSMN_STRING, "key {1", 1, JSMN_PRIMITIVE, "1234")); - js = "\"key 1\": {1234}"; - check(parse(js, JSMN_ERROR_INVAL, 3)); - js = "{\"key 1\"}: 1234"; - check(parse(js, JSMN_ERROR_INVAL, 3)); + js = "{{\"key 1\": 1234}"; + check(parse(js, JSMN_ERROR_PART, 4)); return 0; } -- cgit v1.2.3 From f40811c4ded512135cdf18494ade5e706f01cb6e Mon Sep 17 00:00:00 2001 From: Dario Lombardo Date: Thu, 6 Oct 2016 10:29:35 +0200 Subject: Fix issue in documentation. --- jsmn.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jsmn.h b/jsmn.h index 01ca99c8e..5a5200ee2 100644 --- a/jsmn.h +++ b/jsmn.h @@ -33,9 +33,9 @@ enum jsmnerr { /** * JSON token description. - * @param type type (object, array, string etc.) - * @param start start position in JSON data string - * @param end end position in JSON data string + * type type (object, array, string etc.) + * start start position in JSON data string + * end end position in JSON data string */ typedef struct { jsmntype_t type; -- cgit v1.2.3 From 6572217a0e71fdc13c9ea5571203187f383665e0 Mon Sep 17 00:00:00 2001 From: Paul Rubel Date: Tue, 13 Dec 2016 15:23:23 -0500 Subject: strict checking fails a test, add {}s to fix it The current test looks for success, but since there are no surrounding {}s the test fails. Add them in and add a test like the one that we replace but only in the non-strict code path. --- test/tests.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/test/tests.c b/test/tests.c index d5f0c5300..31761cd33 100644 --- a/test/tests.c +++ b/test/tests.c @@ -353,6 +353,14 @@ int test_nonstrict(void) { JSMN_PRIMITIVE, "Sep", JSMN_PRIMITIVE, "Year", JSMN_PRIMITIVE, "12")); + + //nested {s don't cause a parse error. + js = "\"key {1\": 1234"; + check(parse(js, 2, 2, + JSMN_STRING, "key {1", 1, + JSMN_PRIMITIVE, "1234")); + + #endif return 0; } @@ -367,8 +375,9 @@ int test_unmatched_brackets(void) { check(parse(js, JSMN_ERROR_INVAL, 3)); js = "\"key 1\"}: 1234"; check(parse(js, JSMN_ERROR_INVAL, 3)); - js = "\"key {1\": 1234"; - check(parse(js, 2, 2, + js = "{\"key {1\": 1234}"; + check(parse(js, 3, 3, + JSMN_OBJECT, 0, 16, 1, JSMN_STRING, "key {1", 1, JSMN_PRIMITIVE, "1234")); js = "{{\"key 1\": 1234}"; -- cgit v1.2.3 From fe296583c010821db38748a5dc0eeb4a0a0ce8e1 Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Mon, 1 May 2017 11:42:48 +0300 Subject: added travis.yml --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..1c8ebd327 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: c +sudo: false +script: + - make test -- cgit v1.2.3 From 35086597a72d94d8393e6a90b96e553d714085bd Mon Sep 17 00:00:00 2001 From: "Serge A. Zaitsev" Date: Mon, 1 May 2017 11:44:07 +0300 Subject: added travis badge --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 105897b8c..e58fb2801 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ - JSMN ==== +[![Build Status](https://travis-ci.org/zserge/jsmn.svg?branch=master)](https://travis-ci.org/zserge/jsmn) + jsmn (pronounced like 'jasmine') is a minimalistic JSON parser in C. It can be easily integrated into resource-limited or embedded projects. -- cgit v1.2.3 From f276e23a74f6a2f4342cf2094d99d869408512e9 Mon Sep 17 00:00:00 2001 From: Brian Carcich Date: Thu, 25 Jan 2018 15:38:16 -0500 Subject: btc/typos - JSON_ERROR_... should be JSMN_ERROR_... in README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e58fb2801..45436b36d 100644 --- a/README.md +++ b/README.md @@ -153,9 +153,9 @@ If something goes wrong, you will get an error. Error will be one of these: * `JSMN_ERROR_NOMEM` - not enough tokens, JSON string is too large * `JSMN_ERROR_PART` - JSON string is too short, expecting more JSON data -If you get `JSON_ERROR_NOMEM`, you can re-allocate more tokens and call +If you get `JSMN_ERROR_NOMEM`, you can re-allocate more tokens and call `jsmn_parse` once more. If you read json data from the stream, you can -periodically call `jsmn_parse` and check if return value is `JSON_ERROR_PART`. +periodically call `jsmn_parse` and check if return value is `JSMN_ERROR_PART`. You will get this error until you reach the end of JSON data. Other info -- cgit v1.2.3 From f38f267b62cee4cde1d8e245ce6acc642cb2bf91 Mon Sep 17 00:00:00 2001 From: Alexander Belopolsky Date: Sat, 10 Feb 2018 19:22:23 -0500 Subject: Fixed two typos in a comment. --- example/jsondump.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/jsondump.c b/example/jsondump.c index 4fb10fd51..a9f2c92d0 100644 --- a/example/jsondump.c +++ b/example/jsondump.c @@ -5,11 +5,11 @@ #include #include "../jsmn.h" -/* Function realloc_it() is a wrapper function for standart realloc() +/* Function realloc_it() is a wrapper function for standard realloc() * with one difference - it frees old memory pointer in case of realloc * failure. Thus, DO NOT use old data pointer in anyway after call to * realloc_it(). If your code has some kind of fallback algorithm if - * memory can't be re-allocated - use standart realloc() instead. + * memory can't be re-allocated - use standard realloc() instead. */ static inline void *realloc_it(void *ptrmem, size_t size) { void *p = realloc(ptrmem, size); -- cgit v1.2.3 From 614a36c18cd4865cffafc9089b0e024c6f67d649 Mon Sep 17 00:00:00 2001 From: BenBE Date: Mon, 1 Oct 2018 17:47:01 +0200 Subject: Typo in comment Fixes #84 --- jsmn.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsmn.c b/jsmn.c index bcd6392a0..853c3f17a 100644 --- a/jsmn.c +++ b/jsmn.c @@ -1,7 +1,7 @@ #include "jsmn.h" /** - * Allocates a fresh unused token from the token pull. + * Allocates a fresh unused token from the token pool. */ static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens, size_t num_tokens) { -- cgit v1.2.3 From fdcef3ebf886fa210d14956d3c068a653e76a24e Mon Sep 17 00:00:00 2001 From: Serge Zaitsev Date: Sat, 20 Apr 2019 08:05:39 +0200 Subject: Modernize (#149) * add .clang-format for automated code formatting * automatic code formatting * move config.mk below all target to allow custom non-default build targets * add license to the top of the file * use correct header file guards syntax * convert to single-header, header-only library * update makefile to use jsmn as a header-only library * update readme * add changed from PR #143 * fix clang warnings * add changes from PR #142 * add consts as per PR #134 --- .clang-format | 90 ++++++++ Makefile | 33 ++- README.md | 34 ++- example/jsondump.c | 200 ++++++++--------- example/simple.c | 115 +++++----- jsmn.c | 314 --------------------------- jsmn.h | 446 +++++++++++++++++++++++++++++++++++--- test/test.h | 22 +- test/tests.c | 614 ++++++++++++++++++++++++----------------------------- test/testutil.h | 156 +++++++------- 10 files changed, 1084 insertions(+), 940 deletions(-) create mode 100644 .clang-format delete mode 100644 jsmn.c diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..3a5940ef6 --- /dev/null +++ b/.clang-format @@ -0,0 +1,90 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: false +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|isl|json)/)' + Priority: 3 + - Regex: '.*' + Priority: 1 +IndentCaseLabels: false +IndentWidth: 2 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +ReflowComments: true +SortIncludes: true +SpaceAfterCStyleCast: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 8 +UseTab: Never +... + diff --git a/Makefile b/Makefile index f89701fdd..dcbdd89d7 100644 --- a/Makefile +++ b/Makefile @@ -1,41 +1,36 @@ # You can put your build options here -include config.mk -all: libjsmn.a - -libjsmn.a: jsmn.o - $(AR) rc $@ $^ - -%.o: %.c jsmn.h - $(CC) -c $(CFLAGS) $< -o $@ - test: test_default test_strict test_links test_strict_links -test_default: test/tests.c +test_default: test/tests.c jsmn.h $(CC) $(CFLAGS) $(LDFLAGS) $< -o test/$@ ./test/$@ -test_strict: test/tests.c +test_strict: test/tests.c jsmn.h $(CC) -DJSMN_STRICT=1 $(CFLAGS) $(LDFLAGS) $< -o test/$@ ./test/$@ -test_links: test/tests.c +test_links: test/tests.c jsmn.h $(CC) -DJSMN_PARENT_LINKS=1 $(CFLAGS) $(LDFLAGS) $< -o test/$@ ./test/$@ -test_strict_links: test/tests.c +test_strict_links: test/tests.c jsmn.h $(CC) -DJSMN_STRICT=1 -DJSMN_PARENT_LINKS=1 $(CFLAGS) $(LDFLAGS) $< -o test/$@ ./test/$@ -jsmn_test.o: jsmn_test.c libjsmn.a +simple_example: example/simple.c jsmn.h + $(CC) $(LDFLAGS) $< -o $@ + +jsondump: example/jsondump.c jsmn.h + $(CC) $(LDFLAGS) $< -o $@ -simple_example: example/simple.o libjsmn.a - $(CC) $(LDFLAGS) $^ -o $@ +fmt: + clang-format -i jsmn.h test/*.[ch] example/*.[ch] -jsondump: example/jsondump.o libjsmn.a - $(CC) $(LDFLAGS) $^ -o $@ +lint: + clang-tidy jsmn.h --checks='*' clean: rm -f *.o example/*.o - rm -f *.a *.so rm -f simple_example rm -f jsondump -.PHONY: all clean test +.PHONY: clean test diff --git a/README.md b/README.md index 45436b36d..0f6ed27ab 100644 --- a/README.md +++ b/README.md @@ -76,21 +76,35 @@ object hierarchy. This approach provides enough information for parsing any JSON data and makes it possible to use zero-copy techniques. -Install -------- +Usage +----- -To clone the repository you should have Git installed. Just run: +Download `jsmn.h`, include it, done. - $ git clone https://github.com/zserge/jsmn +``` +#include "jsmn.h" -Repository layout is simple: jsmn.c and jsmn.h are library files, tests are in -the jsmn\_test.c, you will also find README, LICENSE and Makefile files inside. +... +jsmn_parser p; +jsmntok_t t[128]; /* We expect no more than 128 JSON tokens */ -To build the library, run `make`. It is also recommended to run `make test`. -Let me know, if some tests fail. +jsmn_init(&p); +r = jsmn_parse(&p, s, strlen(s), t, 128); +``` -If build was successful, you should get a `libjsmn.a` library. -The header file you should include is called `"jsmn.h"`. +Since jsmn is a single-header, header-only library, for more complex use cases +you might need to define additional macros. `#define JSMN_STATIC` hides all +jsmn API symbols by making them static. Also, if you want to include `jsmn.h` +from multiple C files, to avoid duplication of symbols you may define `JSMN_HEADER` macro. + +``` +/* In every .c file that uses jsmn include only declarations: */ +#define JSMN_HEADER +#include "jsmn.h" + +/* Additionally, create one jsmn.c file for jsmn implementation: */ +#include "jsmn.h" +``` API --- diff --git a/example/jsondump.c b/example/jsondump.c index a9f2c92d0..1eb620640 100644 --- a/example/jsondump.c +++ b/example/jsondump.c @@ -1,9 +1,9 @@ +#include "../jsmn.h" +#include #include #include -#include #include -#include -#include "../jsmn.h" +#include /* Function realloc_it() is a wrapper function for standard realloc() * with one difference - it frees old memory pointer in case of realloc @@ -12,12 +12,12 @@ * memory can't be re-allocated - use standard realloc() instead. */ static inline void *realloc_it(void *ptrmem, size_t size) { - void *p = realloc(ptrmem, size); - if (!p) { - free (ptrmem); - fprintf(stderr, "realloc(): errno=%d\n", errno); - } - return p; + void *p = realloc(ptrmem, size); + if (!p) { + free(ptrmem); + fprintf(stderr, "realloc(): errno=%d\n", errno); + } + return p; } /* @@ -26,101 +26,109 @@ static inline void *realloc_it(void *ptrmem, size_t size) { */ static int dump(const char *js, jsmntok_t *t, size_t count, int indent) { - int i, j, k; - if (count == 0) { - return 0; - } - if (t->type == JSMN_PRIMITIVE) { - printf("%.*s", t->end - t->start, js+t->start); - return 1; - } else if (t->type == JSMN_STRING) { - printf("'%.*s'", t->end - t->start, js+t->start); - return 1; - } else if (t->type == JSMN_OBJECT) { - printf("\n"); - j = 0; - for (i = 0; i < t->size; i++) { - for (k = 0; k < indent; k++) printf(" "); - j += dump(js, t+1+j, count-j, indent+1); - printf(": "); - j += dump(js, t+1+j, count-j, indent+1); - printf("\n"); - } - return j+1; - } else if (t->type == JSMN_ARRAY) { - j = 0; - printf("\n"); - for (i = 0; i < t->size; i++) { - for (k = 0; k < indent-1; k++) printf(" "); - printf(" - "); - j += dump(js, t+1+j, count-j, indent+1); - printf("\n"); - } - return j+1; - } - return 0; + int i, j, k; + jsmntok_t *key; + if (count == 0) { + return 0; + } + if (t->type == JSMN_PRIMITIVE) { + printf("%.*s", t->end - t->start, js + t->start); + return 1; + } else if (t->type == JSMN_STRING) { + printf("'%.*s'", t->end - t->start, js + t->start); + return 1; + } else if (t->type == JSMN_OBJECT) { + printf("\n"); + j = 0; + for (i = 0; i < t->size; i++) { + for (k = 0; k < indent; k++) { + printf(" "); + } + key = t + 1 + j; + j += dump(js, key, count - j, indent + 1); + if (key->size > 0) { + printf(": "); + j += dump(js, t + 1 + j, count - j, indent + 1); + } + printf("\n"); + } + return j + 1; + } else if (t->type == JSMN_ARRAY) { + j = 0; + printf("\n"); + for (i = 0; i < t->size; i++) { + for (k = 0; k < indent - 1; k++) { + printf(" "); + } + printf(" - "); + j += dump(js, t + 1 + j, count - j, indent + 1); + printf("\n"); + } + return j + 1; + } + return 0; } int main() { - int r; - int eof_expected = 0; - char *js = NULL; - size_t jslen = 0; - char buf[BUFSIZ]; + int r; + int eof_expected = 0; + char *js = NULL; + size_t jslen = 0; + char buf[BUFSIZ]; - jsmn_parser p; - jsmntok_t *tok; - size_t tokcount = 2; + jsmn_parser p; + jsmntok_t *tok; + size_t tokcount = 2; - /* Prepare parser */ - jsmn_init(&p); + /* Prepare parser */ + jsmn_init(&p); - /* Allocate some tokens as a start */ - tok = malloc(sizeof(*tok) * tokcount); - if (tok == NULL) { - fprintf(stderr, "malloc(): errno=%d\n", errno); - return 3; - } + /* Allocate some tokens as a start */ + tok = malloc(sizeof(*tok) * tokcount); + if (tok == NULL) { + fprintf(stderr, "malloc(): errno=%d\n", errno); + return 3; + } - for (;;) { - /* Read another chunk */ - r = fread(buf, 1, sizeof(buf), stdin); - if (r < 0) { - fprintf(stderr, "fread(): %d, errno=%d\n", r, errno); - return 1; - } - if (r == 0) { - if (eof_expected != 0) { - return 0; - } else { - fprintf(stderr, "fread(): unexpected EOF\n"); - return 2; - } - } + for (;;) { + /* Read another chunk */ + r = fread(buf, 1, sizeof(buf), stdin); + if (r < 0) { + fprintf(stderr, "fread(): %d, errno=%d\n", r, errno); + return 1; + } + if (r == 0) { + if (eof_expected != 0) { + return 0; + } else { + fprintf(stderr, "fread(): unexpected EOF\n"); + return 2; + } + } - js = realloc_it(js, jslen + r + 1); - if (js == NULL) { - return 3; - } - strncpy(js + jslen, buf, r); - jslen = jslen + r; + js = realloc_it(js, jslen + r + 1); + if (js == NULL) { + return 3; + } + strncpy(js + jslen, buf, r); + jslen = jslen + r; -again: - r = jsmn_parse(&p, js, jslen, tok, tokcount); - if (r < 0) { - if (r == JSMN_ERROR_NOMEM) { - tokcount = tokcount * 2; - tok = realloc_it(tok, sizeof(*tok) * tokcount); - if (tok == NULL) { - return 3; - } - goto again; - } - } else { - dump(js, tok, p.toknext, 0); - eof_expected = 1; - } - } + again: + r = jsmn_parse(&p, js, jslen, tok, tokcount); + if (r < 0) { + if (r == JSMN_ERROR_NOMEM) { + tokcount = tokcount * 2; + tok = realloc_it(tok, sizeof(*tok) * tokcount); + if (tok == NULL) { + return 3; + } + goto again; + } + } else { + dump(js, tok, p.toknext, 0); + eof_expected = 1; + } + } - return EXIT_SUCCESS; + return EXIT_SUCCESS; } diff --git a/example/simple.c b/example/simple.c index aeb9cf47d..1254575a1 100644 --- a/example/simple.c +++ b/example/simple.c @@ -1,7 +1,7 @@ +#include "../jsmn.h" #include #include #include -#include "../jsmn.h" /* * A small example of jsmn parsing when JSON structure is known and number of @@ -9,68 +9,69 @@ */ static const char *JSON_STRING = - "{\"user\": \"johndoe\", \"admin\": false, \"uid\": 1000,\n " - "\"groups\": [\"users\", \"wheel\", \"audio\", \"video\"]}"; + "{\"user\": \"johndoe\", \"admin\": false, \"uid\": 1000,\n " + "\"groups\": [\"users\", \"wheel\", \"audio\", \"video\"]}"; static int jsoneq(const char *json, jsmntok_t *tok, const char *s) { - if (tok->type == JSMN_STRING && (int) strlen(s) == tok->end - tok->start && - strncmp(json + tok->start, s, tok->end - tok->start) == 0) { - return 0; - } - return -1; + if (tok->type == JSMN_STRING && (int)strlen(s) == tok->end - tok->start && + strncmp(json + tok->start, s, tok->end - tok->start) == 0) { + return 0; + } + return -1; } int main() { - int i; - int r; - jsmn_parser p; - jsmntok_t t[128]; /* We expect no more than 128 tokens */ + int i; + int r; + jsmn_parser p; + jsmntok_t t[128]; /* We expect no more than 128 tokens */ - jsmn_init(&p); - r = jsmn_parse(&p, JSON_STRING, strlen(JSON_STRING), t, sizeof(t)/sizeof(t[0])); - if (r < 0) { - printf("Failed to parse JSON: %d\n", r); - return 1; - } + jsmn_init(&p); + r = jsmn_parse(&p, JSON_STRING, strlen(JSON_STRING), t, + sizeof(t) / sizeof(t[0])); + if (r < 0) { + printf("Failed to parse JSON: %d\n", r); + return 1; + } - /* Assume the top-level element is an object */ - if (r < 1 || t[0].type != JSMN_OBJECT) { - printf("Object expected\n"); - return 1; - } + /* Assume the top-level element is an object */ + if (r < 1 || t[0].type != JSMN_OBJECT) { + printf("Object expected\n"); + return 1; + } - /* Loop over all keys of the root object */ - for (i = 1; i < r; i++) { - if (jsoneq(JSON_STRING, &t[i], "user") == 0) { - /* We may use strndup() to fetch string value */ - printf("- User: %.*s\n", t[i+1].end-t[i+1].start, - JSON_STRING + t[i+1].start); - i++; - } else if (jsoneq(JSON_STRING, &t[i], "admin") == 0) { - /* We may additionally check if the value is either "true" or "false" */ - printf("- Admin: %.*s\n", t[i+1].end-t[i+1].start, - JSON_STRING + t[i+1].start); - i++; - } else if (jsoneq(JSON_STRING, &t[i], "uid") == 0) { - /* We may want to do strtol() here to get numeric value */ - printf("- UID: %.*s\n", t[i+1].end-t[i+1].start, - JSON_STRING + t[i+1].start); - i++; - } else if (jsoneq(JSON_STRING, &t[i], "groups") == 0) { - int j; - printf("- Groups:\n"); - if (t[i+1].type != JSMN_ARRAY) { - continue; /* We expect groups to be an array of strings */ - } - for (j = 0; j < t[i+1].size; j++) { - jsmntok_t *g = &t[i+j+2]; - printf(" * %.*s\n", g->end - g->start, JSON_STRING + g->start); - } - i += t[i+1].size + 1; - } else { - printf("Unexpected key: %.*s\n", t[i].end-t[i].start, - JSON_STRING + t[i].start); - } - } - return EXIT_SUCCESS; + /* Loop over all keys of the root object */ + for (i = 1; i < r; i++) { + if (jsoneq(JSON_STRING, &t[i], "user") == 0) { + /* We may use strndup() to fetch string value */ + printf("- User: %.*s\n", t[i + 1].end - t[i + 1].start, + JSON_STRING + t[i + 1].start); + i++; + } else if (jsoneq(JSON_STRING, &t[i], "admin") == 0) { + /* We may additionally check if the value is either "true" or "false" */ + printf("- Admin: %.*s\n", t[i + 1].end - t[i + 1].start, + JSON_STRING + t[i + 1].start); + i++; + } else if (jsoneq(JSON_STRING, &t[i], "uid") == 0) { + /* We may want to do strtol() here to get numeric value */ + printf("- UID: %.*s\n", t[i + 1].end - t[i + 1].start, + JSON_STRING + t[i + 1].start); + i++; + } else if (jsoneq(JSON_STRING, &t[i], "groups") == 0) { + int j; + printf("- Groups:\n"); + if (t[i + 1].type != JSMN_ARRAY) { + continue; /* We expect groups to be an array of strings */ + } + for (j = 0; j < t[i + 1].size; j++) { + jsmntok_t *g = &t[i + j + 2]; + printf(" * %.*s\n", g->end - g->start, JSON_STRING + g->start); + } + i += t[i + 1].size + 1; + } else { + printf("Unexpected key: %.*s\n", t[i].end - t[i].start, + JSON_STRING + t[i].start); + } + } + return EXIT_SUCCESS; } diff --git a/jsmn.c b/jsmn.c deleted file mode 100644 index 853c3f17a..000000000 --- a/jsmn.c +++ /dev/null @@ -1,314 +0,0 @@ -#include "jsmn.h" - -/** - * Allocates a fresh unused token from the token pool. - */ -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 = tok->end = -1; - tok->size = 0; -#ifdef JSMN_PARENT_LINKS - tok->parent = -1; -#endif - return tok; -} - -/** - * Fills token type and boundaries. - */ -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; -} - -/** - * Fills next available token with JSON primitive. - */ -static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, - size_t len, jsmntok_t *tokens, size_t num_tokens) { - jsmntok_t *token; - int start; - - start = parser->pos; - - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { - switch (js[parser->pos]) { -#ifndef JSMN_STRICT - /* In strict mode primitive must be followed by "," or "}" or "]" */ - case ':': -#endif - case '\t' : case '\r' : case '\n' : case ' ' : - case ',' : case ']' : case '}' : - goto found; - } - if (js[parser->pos] < 32 || js[parser->pos] >= 127) { - parser->pos = start; - return JSMN_ERROR_INVAL; - } - } -#ifdef JSMN_STRICT - /* In strict mode primitive must be followed by a comma/object/array */ - parser->pos = start; - return JSMN_ERROR_PART; -#endif - -found: - if (tokens == NULL) { - parser->pos--; - return 0; - } - token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) { - parser->pos = start; - return JSMN_ERROR_NOMEM; - } - jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); -#ifdef JSMN_PARENT_LINKS - token->parent = parser->toksuper; -#endif - parser->pos--; - return 0; -} - -/** - * Fills next token with JSON string. - */ -static int jsmn_parse_string(jsmn_parser *parser, const char *js, - size_t len, jsmntok_t *tokens, size_t num_tokens) { - jsmntok_t *token; - - int start = parser->pos; - - parser->pos++; - - /* Skip starting quote */ - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { - char c = js[parser->pos]; - - /* Quote: end of string */ - if (c == '\"') { - if (tokens == NULL) { - return 0; - } - token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) { - parser->pos = start; - return JSMN_ERROR_NOMEM; - } - jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos); -#ifdef JSMN_PARENT_LINKS - token->parent = parser->toksuper; -#endif - return 0; - } - - /* Backslash: Quoted symbol expected */ - if (c == '\\' && parser->pos + 1 < len) { - int i; - parser->pos++; - switch (js[parser->pos]) { - /* Allowed escaped symbols */ - case '\"': case '/' : case '\\' : case 'b' : - case 'f' : case 'r' : case 'n' : case 't' : - break; - /* Allows escaped symbol \uXXXX */ - case 'u': - parser->pos++; - for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) { - /* If it isn't a hex character we have an error */ - if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ - (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ - (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ - parser->pos = start; - return JSMN_ERROR_INVAL; - } - parser->pos++; - } - parser->pos--; - break; - /* Unexpected symbol */ - default: - parser->pos = start; - return JSMN_ERROR_INVAL; - } - } - } - parser->pos = start; - return JSMN_ERROR_PART; -} - -/** - * Parse JSON string and fill tokens. - */ -int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, - jsmntok_t *tokens, unsigned int num_tokens) { - int r; - int i; - jsmntok_t *token; - int count = parser->toknext; - - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { - char c; - jsmntype_t type; - - c = js[parser->pos]; - switch (c) { - case '{': case '[': - count++; - if (tokens == NULL) { - break; - } - token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) - return JSMN_ERROR_NOMEM; - if (parser->toksuper != -1) { - tokens[parser->toksuper].size++; -#ifdef JSMN_PARENT_LINKS - token->parent = parser->toksuper; -#endif - } - token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); - token->start = parser->pos; - parser->toksuper = parser->toknext - 1; - break; - case '}': case ']': - if (tokens == NULL) - break; - type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); -#ifdef JSMN_PARENT_LINKS - if (parser->toknext < 1) { - return JSMN_ERROR_INVAL; - } - token = &tokens[parser->toknext - 1]; - for (;;) { - if (token->start != -1 && token->end == -1) { - if (token->type != type) { - return JSMN_ERROR_INVAL; - } - token->end = parser->pos + 1; - parser->toksuper = token->parent; - break; - } - if (token->parent == -1) { - if(token->type != type || parser->toksuper == -1) { - return JSMN_ERROR_INVAL; - } - break; - } - token = &tokens[token->parent]; - } -#else - for (i = parser->toknext - 1; i >= 0; i--) { - token = &tokens[i]; - if (token->start != -1 && token->end == -1) { - if (token->type != type) { - return JSMN_ERROR_INVAL; - } - parser->toksuper = -1; - token->end = parser->pos + 1; - break; - } - } - /* Error if unmatched closing bracket */ - if (i == -1) return JSMN_ERROR_INVAL; - for (; i >= 0; i--) { - token = &tokens[i]; - if (token->start != -1 && token->end == -1) { - parser->toksuper = i; - break; - } - } -#endif - break; - case '\"': - r = jsmn_parse_string(parser, js, len, tokens, num_tokens); - if (r < 0) return r; - count++; - if (parser->toksuper != -1 && tokens != NULL) - tokens[parser->toksuper].size++; - break; - case '\t' : case '\r' : case '\n' : case ' ': - break; - case ':': - parser->toksuper = parser->toknext - 1; - break; - case ',': - if (tokens != NULL && parser->toksuper != -1 && - tokens[parser->toksuper].type != JSMN_ARRAY && - tokens[parser->toksuper].type != JSMN_OBJECT) { -#ifdef JSMN_PARENT_LINKS - parser->toksuper = tokens[parser->toksuper].parent; -#else - for (i = parser->toknext - 1; i >= 0; i--) { - if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { - if (tokens[i].start != -1 && tokens[i].end == -1) { - parser->toksuper = i; - break; - } - } - } -#endif - } - break; -#ifdef JSMN_STRICT - /* In strict mode primitives are: numbers and booleans */ - case '-': case '0': case '1' : case '2': case '3' : case '4': - case '5': case '6': case '7' : case '8': case '9': - case 't': case 'f': case 'n' : - /* And they must not be keys of the object */ - if (tokens != NULL && parser->toksuper != -1) { - jsmntok_t *t = &tokens[parser->toksuper]; - if (t->type == JSMN_OBJECT || - (t->type == JSMN_STRING && t->size != 0)) { - return JSMN_ERROR_INVAL; - } - } -#else - /* In non-strict mode every unquoted value is a primitive */ - default: -#endif - r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); - if (r < 0) return r; - count++; - if (parser->toksuper != -1 && tokens != NULL) - tokens[parser->toksuper].size++; - break; - -#ifdef JSMN_STRICT - /* Unexpected char in strict mode */ - default: - return JSMN_ERROR_INVAL; -#endif - } - } - - if (tokens != NULL) { - for (i = parser->toknext - 1; i >= 0; i--) { - /* Unmatched opened object or array */ - if (tokens[i].start != -1 && tokens[i].end == -1) { - return JSMN_ERROR_PART; - } - } - } - - return count; -} - -/** - * Creates a new parser based over a given buffer with an array of tokens - * available. - */ -void jsmn_init(jsmn_parser *parser) { - parser->pos = 0; - parser->toknext = 0; - parser->toksuper = -1; -} - diff --git a/jsmn.h b/jsmn.h index 5a5200ee2..b95368a20 100644 --- a/jsmn.h +++ b/jsmn.h @@ -1,5 +1,28 @@ -#ifndef __JSMN_H_ -#define __JSMN_H_ +/* + * MIT License + * + * Copyright (c) 2010 Serge Zaitsev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef JSMN_H +#define JSMN_H #include @@ -7,6 +30,12 @@ extern "C" { #endif +#ifdef JSMN_STATIC +#define JSMN_API static +#else +#define JSMN_API extern +#endif + /** * JSON type identifier. Basic types are: * o Object @@ -15,20 +44,20 @@ extern "C" { * o Other primitive: number, boolean (true/false) or null */ typedef enum { - JSMN_UNDEFINED = 0, - JSMN_OBJECT = 1, - JSMN_ARRAY = 2, - JSMN_STRING = 3, - JSMN_PRIMITIVE = 4 + JSMN_UNDEFINED = 0, + JSMN_OBJECT = 1, + JSMN_ARRAY = 2, + JSMN_STRING = 3, + JSMN_PRIMITIVE = 4 } jsmntype_t; enum jsmnerr { - /* Not enough tokens were provided */ - JSMN_ERROR_NOMEM = -1, - /* Invalid character inside JSON string */ - JSMN_ERROR_INVAL = -2, - /* The string is not a full JSON packet, more bytes expected */ - JSMN_ERROR_PART = -3 + /* Not enough tokens were provided */ + JSMN_ERROR_NOMEM = -1, + /* Invalid character inside JSON string */ + JSMN_ERROR_INVAL = -2, + /* The string is not a full JSON packet, more bytes expected */ + JSMN_ERROR_PART = -3 }; /** @@ -38,39 +67,402 @@ enum jsmnerr { * end end position in JSON data string */ typedef struct { - jsmntype_t type; - int start; - int end; - int size; + jsmntype_t type; + int start; + int end; + int size; #ifdef JSMN_PARENT_LINKS - int parent; + int parent; #endif } jsmntok_t; /** * JSON parser. Contains an array of token blocks available. Also stores - * the string being parsed now and current position in that string + * the string being parsed now and current position in that string. */ typedef struct { - unsigned int pos; /* offset in the JSON string */ - unsigned int toknext; /* next token to allocate */ - int toksuper; /* superior token node, e.g parent object or array */ + unsigned int pos; /* offset in the JSON string */ + unsigned int toknext; /* next token to allocate */ + int toksuper; /* superior token node, e.g. parent object or array */ } jsmn_parser; /** * Create JSON parser over an array of tokens */ -void jsmn_init(jsmn_parser *parser); +JSMN_API void jsmn_init(jsmn_parser *parser); /** - * Run JSON parser. It parses a JSON data string into and array of tokens, each describing + * Run JSON parser. It parses a JSON data string into and array of tokens, each + * describing * a single JSON object. */ -int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, - jsmntok_t *tokens, unsigned int num_tokens); +JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, + jsmntok_t *tokens, const unsigned int num_tokens); + +#ifndef JSMN_HEADER +/** + * Allocates a fresh unused token from the token pool. + */ +static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens, + const size_t num_tokens) { + jsmntok_t *tok; + if (parser->toknext >= num_tokens) { + return NULL; + } + tok = &tokens[parser->toknext++]; + tok->start = tok->end = -1; + tok->size = 0; +#ifdef JSMN_PARENT_LINKS + tok->parent = -1; +#endif + return tok; +} + +/** + * Fills token type and boundaries. + */ +static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type, + const int start, const int end) { + token->type = type; + token->start = start; + token->end = end; + token->size = 0; +} + +/** + * Fills next available token with JSON primitive. + */ +static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, + const size_t len, jsmntok_t *tokens, + const size_t num_tokens) { + jsmntok_t *token; + int start; + + start = parser->pos; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + switch (js[parser->pos]) { +#ifndef JSMN_STRICT + /* In strict mode primitive must be followed by "," or "}" or "]" */ + case ':': +#endif + case '\t': + case '\r': + case '\n': + case ' ': + case ',': + case ']': + case '}': + goto found; + } + if (js[parser->pos] < 32 || js[parser->pos] >= 127) { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } +#ifdef JSMN_STRICT + /* In strict mode primitive must be followed by a comma/object/array */ + parser->pos = start; + return JSMN_ERROR_PART; +#endif + +found: + if (tokens == NULL) { + parser->pos--; + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + parser->pos--; + return 0; +} + +/** + * Fills next token with JSON string. + */ +static int jsmn_parse_string(jsmn_parser *parser, const char *js, + const size_t len, jsmntok_t *tokens, + const size_t num_tokens) { + jsmntok_t *token; + + int start = parser->pos; + + parser->pos++; + + /* Skip starting quote */ + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c = js[parser->pos]; + + /* Quote: end of string */ + if (c == '\"') { + if (tokens == NULL) { + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + return 0; + } + + /* Backslash: Quoted symbol expected */ + if (c == '\\' && parser->pos + 1 < len) { + int i; + parser->pos++; + switch (js[parser->pos]) { + /* Allowed escaped symbols */ + case '\"': + case '/': + case '\\': + case 'b': + case 'f': + case 'r': + case 'n': + case 't': + break; + /* Allows escaped symbol \uXXXX */ + case 'u': + parser->pos++; + for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; + i++) { + /* If it isn't a hex character we have an error */ + if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ + (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ + (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ + parser->pos = start; + return JSMN_ERROR_INVAL; + } + parser->pos++; + } + parser->pos--; + break; + /* Unexpected symbol */ + default: + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } + } + parser->pos = start; + return JSMN_ERROR_PART; +} + +/** + * Parse JSON string and fill tokens. + */ +JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, + jsmntok_t *tokens, const unsigned int num_tokens) { + int r; + int i; + jsmntok_t *token; + int count = parser->toknext; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c; + jsmntype_t type; + + c = js[parser->pos]; + switch (c) { + case '{': + case '[': + count++; + if (tokens == NULL) { + break; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + return JSMN_ERROR_NOMEM; + } + if (parser->toksuper != -1) { + jsmntok_t *t = &tokens[parser->toksuper]; +#ifdef JSMN_STRICT + /* In strict mode an object or array can't become a key */ + if (t->type == JSMN_OBJECT) { + return JSMN_ERROR_INVAL; + } +#endif + t->size++; +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + } + token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); + token->start = parser->pos; + parser->toksuper = parser->toknext - 1; + break; + case '}': + case ']': + if (tokens == NULL) { + break; + } + type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); +#ifdef JSMN_PARENT_LINKS + if (parser->toknext < 1) { + return JSMN_ERROR_INVAL; + } + token = &tokens[parser->toknext - 1]; + for (;;) { + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + token->end = parser->pos + 1; + parser->toksuper = token->parent; + break; + } + if (token->parent == -1) { + if (token->type != type || parser->toksuper == -1) { + return JSMN_ERROR_INVAL; + } + break; + } + token = &tokens[token->parent]; + } +#else + for (i = parser->toknext - 1; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + parser->toksuper = -1; + token->end = parser->pos + 1; + break; + } + } + /* Error if unmatched closing bracket */ + if (i == -1) { + return JSMN_ERROR_INVAL; + } + for (; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + parser->toksuper = i; + break; + } + } +#endif + break; + case '\"': + r = jsmn_parse_string(parser, js, len, tokens, num_tokens); + if (r < 0) { + return r; + } + count++; + if (parser->toksuper != -1 && tokens != NULL) { + tokens[parser->toksuper].size++; + } + break; + case '\t': + case '\r': + case '\n': + case ' ': + break; + case ':': + parser->toksuper = parser->toknext - 1; + break; + case ',': + if (tokens != NULL && parser->toksuper != -1 && + tokens[parser->toksuper].type != JSMN_ARRAY && + tokens[parser->toksuper].type != JSMN_OBJECT) { +#ifdef JSMN_PARENT_LINKS + parser->toksuper = tokens[parser->toksuper].parent; +#else + for (i = parser->toknext - 1; i >= 0; i--) { + if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { + if (tokens[i].start != -1 && tokens[i].end == -1) { + parser->toksuper = i; + break; + } + } + } +#endif + } + break; +#ifdef JSMN_STRICT + /* In strict mode primitives are: numbers and booleans */ + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 't': + case 'f': + case 'n': + /* And they must not be keys of the object */ + if (tokens != NULL && parser->toksuper != -1) { + const jsmntok_t *t = &tokens[parser->toksuper]; + if (t->type == JSMN_OBJECT || + (t->type == JSMN_STRING && t->size != 0)) { + return JSMN_ERROR_INVAL; + } + } +#else + /* In non-strict mode every unquoted value is a primitive */ + default: +#endif + r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); + if (r < 0) { + return r; + } + count++; + if (parser->toksuper != -1 && tokens != NULL) { + tokens[parser->toksuper].size++; + } + break; + +#ifdef JSMN_STRICT + /* Unexpected char in strict mode */ + default: + return JSMN_ERROR_INVAL; +#endif + } + } + + if (tokens != NULL) { + for (i = parser->toknext - 1; i >= 0; i--) { + /* Unmatched opened object or array */ + if (tokens[i].start != -1 && tokens[i].end == -1) { + return JSMN_ERROR_PART; + } + } + } + + return count; +} + +/** + * Creates a new parser based over a given buffer with an array of tokens + * available. + */ +JSMN_API void jsmn_init(jsmn_parser *parser) { + parser->pos = 0; + parser->toknext = 0; + parser->toksuper = -1; +} + +#endif /* JSMN_HEADER */ #ifdef __cplusplus } #endif -#endif /* __JSMN_H_ */ +#endif /* JSMN_H */ diff --git a/test/test.h b/test/test.h index 35f704f99..a1c0957a7 100644 --- a/test/test.h +++ b/test/test.h @@ -5,23 +5,27 @@ static int test_passed = 0; static int test_failed = 0; /* Terminate current test with error */ -#define fail() return __LINE__ +#define fail() return __LINE__ /* Successful end of the test case */ #define done() return 0 /* Check single condition */ -#define check(cond) do { if (!(cond)) fail(); } while (0) +#define check(cond) \ + do { \ + if (!(cond)) \ + fail(); \ + } while (0) /* Test runner */ static void test(int (*func)(void), const char *name) { - int r = func(); - if (r == 0) { - test_passed++; - } else { - test_failed++; - printf("FAILED: %s (at line %d)\n", name, r); - } + int r = func(); + if (r == 0) { + test_passed++; + } else { + test_failed++; + printf("FAILED: %s (at line %d)\n", name, r); + } } #endif /* __TEST_H__ */ diff --git a/test/tests.c b/test/tests.c index 31761cd33..d8a4d922e 100644 --- a/test/tests.c +++ b/test/tests.c @@ -1,407 +1,359 @@ +#include #include #include #include -#include #include "test.h" #include "testutil.h" int test_empty(void) { - check(parse("{}", 1, 1, - JSMN_OBJECT, 0, 2, 0)); - check(parse("[]", 1, 1, - JSMN_ARRAY, 0, 2, 0)); - check(parse("[{},{}]", 3, 3, - JSMN_ARRAY, 0, 7, 2, - JSMN_OBJECT, 1, 3, 0, - JSMN_OBJECT, 4, 6, 0)); - return 0; + check(parse("{}", 1, 1, JSMN_OBJECT, 0, 2, 0)); + check(parse("[]", 1, 1, JSMN_ARRAY, 0, 2, 0)); + check(parse("[{},{}]", 3, 3, JSMN_ARRAY, 0, 7, 2, JSMN_OBJECT, 1, 3, 0, + JSMN_OBJECT, 4, 6, 0)); + return 0; } int test_object(void) { - check(parse("{\"a\":0}", 3, 3, - JSMN_OBJECT, 0, 7, 1, - JSMN_STRING, "a", 1, - JSMN_PRIMITIVE, "0")); - check(parse("{\"a\":[]}", 3, 3, - JSMN_OBJECT, 0, 8, 1, - JSMN_STRING, "a", 1, - JSMN_ARRAY, 5, 7, 0)); - check(parse("{\"a\":{},\"b\":{}}", 5, 5, - JSMN_OBJECT, -1, -1, 2, - JSMN_STRING, "a", 1, - JSMN_OBJECT, -1, -1, 0, - JSMN_STRING, "b", 1, - JSMN_OBJECT, -1, -1, 0)); - check(parse("{\n \"Day\": 26,\n \"Month\": 9,\n \"Year\": 12\n }", 7, 7, - JSMN_OBJECT, -1, -1, 3, - JSMN_STRING, "Day", 1, - JSMN_PRIMITIVE, "26", - JSMN_STRING, "Month", 1, - JSMN_PRIMITIVE, "9", - JSMN_STRING, "Year", 1, - JSMN_PRIMITIVE, "12")); - check(parse("{\"a\": 0, \"b\": \"c\"}", 5, 5, - JSMN_OBJECT, -1, -1, 2, - JSMN_STRING, "a", 1, - JSMN_PRIMITIVE, "0", - JSMN_STRING, "b", 1, - JSMN_STRING, "c", 0)); + check(parse("{\"a\":0}", 3, 3, JSMN_OBJECT, 0, 7, 1, JSMN_STRING, "a", 1, + JSMN_PRIMITIVE, "0")); + check(parse("{\"a\":[]}", 3, 3, JSMN_OBJECT, 0, 8, 1, JSMN_STRING, "a", 1, + JSMN_ARRAY, 5, 7, 0)); + check(parse("{\"a\":{},\"b\":{}}", 5, 5, JSMN_OBJECT, -1, -1, 2, JSMN_STRING, + "a", 1, JSMN_OBJECT, -1, -1, 0, JSMN_STRING, "b", 1, JSMN_OBJECT, + -1, -1, 0)); + check(parse("{\n \"Day\": 26,\n \"Month\": 9,\n \"Year\": 12\n }", 7, 7, + JSMN_OBJECT, -1, -1, 3, JSMN_STRING, "Day", 1, JSMN_PRIMITIVE, + "26", JSMN_STRING, "Month", 1, JSMN_PRIMITIVE, "9", JSMN_STRING, + "Year", 1, JSMN_PRIMITIVE, "12")); + check(parse("{\"a\": 0, \"b\": \"c\"}", 5, 5, JSMN_OBJECT, -1, -1, 2, + JSMN_STRING, "a", 1, JSMN_PRIMITIVE, "0", JSMN_STRING, "b", 1, + JSMN_STRING, "c", 0)); #ifdef JSMN_STRICT - check(parse("{\"a\"\n0}", JSMN_ERROR_INVAL, 3)); - check(parse("{\"a\", 0}", JSMN_ERROR_INVAL, 3)); - check(parse("{\"a\": {2}}", JSMN_ERROR_INVAL, 3)); - check(parse("{\"a\": {2: 3}}", JSMN_ERROR_INVAL, 3)); - check(parse("{\"a\": {\"a\": 2 3}}", JSMN_ERROR_INVAL, 5)); - /* FIXME */ - /*check(parse("{\"a\"}", JSMN_ERROR_INVAL, 2));*/ - /*check(parse("{\"a\": 1, \"b\"}", JSMN_ERROR_INVAL, 4));*/ - /*check(parse("{\"a\",\"b\":1}", JSMN_ERROR_INVAL, 4));*/ - /*check(parse("{\"a\":1,}", JSMN_ERROR_INVAL, 4));*/ - /*check(parse("{\"a\":\"b\":\"c\"}", JSMN_ERROR_INVAL, 4));*/ - /*check(parse("{,}", JSMN_ERROR_INVAL, 4));*/ + check(parse("{\"a\"\n0}", JSMN_ERROR_INVAL, 3)); + check(parse("{\"a\", 0}", JSMN_ERROR_INVAL, 3)); + check(parse("{\"a\": {2}}", JSMN_ERROR_INVAL, 3)); + check(parse("{\"a\": {2: 3}}", JSMN_ERROR_INVAL, 3)); + check(parse("{\"a\": {\"a\": 2 3}}", JSMN_ERROR_INVAL, 5)); +/* FIXME */ +/*check(parse("{\"a\"}", JSMN_ERROR_INVAL, 2));*/ +/*check(parse("{\"a\": 1, \"b\"}", JSMN_ERROR_INVAL, 4));*/ +/*check(parse("{\"a\",\"b\":1}", JSMN_ERROR_INVAL, 4));*/ +/*check(parse("{\"a\":1,}", JSMN_ERROR_INVAL, 4));*/ +/*check(parse("{\"a\":\"b\":\"c\"}", JSMN_ERROR_INVAL, 4));*/ +/*check(parse("{,}", JSMN_ERROR_INVAL, 4));*/ #endif - return 0; + return 0; } int test_array(void) { - /* FIXME */ - /*check(parse("[10}", JSMN_ERROR_INVAL, 3));*/ - /*check(parse("[1,,3]", JSMN_ERROR_INVAL, 3)*/ - check(parse("[10]", 2, 2, - JSMN_ARRAY, -1, -1, 1, - JSMN_PRIMITIVE, "10")); - check(parse("{\"a\": 1]", JSMN_ERROR_INVAL, 3)); - /* FIXME */ - /*check(parse("[\"a\": 1]", JSMN_ERROR_INVAL, 3));*/ - return 0; + /* FIXME */ + /*check(parse("[10}", JSMN_ERROR_INVAL, 3));*/ + /*check(parse("[1,,3]", JSMN_ERROR_INVAL, 3)*/ + check(parse("[10]", 2, 2, JSMN_ARRAY, -1, -1, 1, JSMN_PRIMITIVE, "10")); + check(parse("{\"a\": 1]", JSMN_ERROR_INVAL, 3)); + /* FIXME */ + /*check(parse("[\"a\": 1]", JSMN_ERROR_INVAL, 3));*/ + return 0; } int test_primitive(void) { - check(parse("{\"boolVar\" : true }", 3, 3, - JSMN_OBJECT, -1, -1, 1, - JSMN_STRING, "boolVar", 1, - JSMN_PRIMITIVE, "true")); - check(parse("{\"boolVar\" : false }", 3, 3, - JSMN_OBJECT, -1, -1, 1, - JSMN_STRING, "boolVar", 1, - JSMN_PRIMITIVE, "false")); - check(parse("{\"nullVar\" : null }", 3, 3, - JSMN_OBJECT, -1, -1, 1, - JSMN_STRING, "nullVar", 1, - JSMN_PRIMITIVE, "null")); - check(parse("{\"intVar\" : 12}", 3, 3, - JSMN_OBJECT, -1, -1, 1, - JSMN_STRING, "intVar", 1, - JSMN_PRIMITIVE, "12")); - check(parse("{\"floatVar\" : 12.345}", 3, 3, - JSMN_OBJECT, -1, -1, 1, - JSMN_STRING, "floatVar", 1, - JSMN_PRIMITIVE, "12.345")); - return 0; + check(parse("{\"boolVar\" : true }", 3, 3, JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "boolVar", 1, JSMN_PRIMITIVE, "true")); + check(parse("{\"boolVar\" : false }", 3, 3, JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "boolVar", 1, JSMN_PRIMITIVE, "false")); + check(parse("{\"nullVar\" : null }", 3, 3, JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "nullVar", 1, JSMN_PRIMITIVE, "null")); + check(parse("{\"intVar\" : 12}", 3, 3, JSMN_OBJECT, -1, -1, 1, JSMN_STRING, + "intVar", 1, JSMN_PRIMITIVE, "12")); + check(parse("{\"floatVar\" : 12.345}", 3, 3, JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "floatVar", 1, JSMN_PRIMITIVE, "12.345")); + return 0; } int test_string(void) { - check(parse("{\"strVar\" : \"hello world\"}", 3, 3, - JSMN_OBJECT, -1, -1, 1, - JSMN_STRING, "strVar", 1, - JSMN_STRING, "hello world", 0)); - check(parse("{\"strVar\" : \"escapes: \\/\\r\\n\\t\\b\\f\\\"\\\\\"}", 3, 3, - JSMN_OBJECT, -1, -1, 1, - JSMN_STRING, "strVar", 1, - JSMN_STRING, "escapes: \\/\\r\\n\\t\\b\\f\\\"\\\\", 0)); - check(parse("{\"strVar\": \"\"}", 3, 3, - JSMN_OBJECT, -1, -1, 1, - JSMN_STRING, "strVar", 1, - JSMN_STRING, "", 0)); - check(parse("{\"a\":\"\\uAbcD\"}", 3, 3, - JSMN_OBJECT, -1, -1, 1, - JSMN_STRING, "a", 1, - JSMN_STRING, "\\uAbcD", 0)); - check(parse("{\"a\":\"str\\u0000\"}", 3, 3, - JSMN_OBJECT, -1, -1, 1, - JSMN_STRING, "a", 1, - JSMN_STRING, "str\\u0000", 0)); - check(parse("{\"a\":\"\\uFFFFstr\"}", 3, 3, - JSMN_OBJECT, -1, -1, 1, - JSMN_STRING, "a", 1, - JSMN_STRING, "\\uFFFFstr", 0)); - check(parse("{\"a\":[\"\\u0280\"]}", 4, 4, - JSMN_OBJECT, -1, -1, 1, - JSMN_STRING, "a", 1, - JSMN_ARRAY, -1, -1, 1, - JSMN_STRING, "\\u0280", 0)); - - check(parse("{\"a\":\"str\\uFFGFstr\"}", JSMN_ERROR_INVAL, 3)); - check(parse("{\"a\":\"str\\u@FfF\"}", JSMN_ERROR_INVAL, 3)); - check(parse("{{\"a\":[\"\\u028\"]}", JSMN_ERROR_INVAL, 4)); - return 0; + check(parse("{\"strVar\" : \"hello world\"}", 3, 3, JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "strVar", 1, JSMN_STRING, "hello world", 0)); + check(parse("{\"strVar\" : \"escapes: \\/\\r\\n\\t\\b\\f\\\"\\\\\"}", 3, 3, + JSMN_OBJECT, -1, -1, 1, JSMN_STRING, "strVar", 1, JSMN_STRING, + "escapes: \\/\\r\\n\\t\\b\\f\\\"\\\\", 0)); + check(parse("{\"strVar\": \"\"}", 3, 3, JSMN_OBJECT, -1, -1, 1, JSMN_STRING, + "strVar", 1, JSMN_STRING, "", 0)); + check(parse("{\"a\":\"\\uAbcD\"}", 3, 3, JSMN_OBJECT, -1, -1, 1, JSMN_STRING, + "a", 1, JSMN_STRING, "\\uAbcD", 0)); + check(parse("{\"a\":\"str\\u0000\"}", 3, 3, JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "a", 1, JSMN_STRING, "str\\u0000", 0)); + check(parse("{\"a\":\"\\uFFFFstr\"}", 3, 3, JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "a", 1, JSMN_STRING, "\\uFFFFstr", 0)); + check(parse("{\"a\":[\"\\u0280\"]}", 4, 4, JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "a", 1, JSMN_ARRAY, -1, -1, 1, JSMN_STRING, + "\\u0280", 0)); + + check(parse("{\"a\":\"str\\uFFGFstr\"}", JSMN_ERROR_INVAL, 3)); + check(parse("{\"a\":\"str\\u@FfF\"}", JSMN_ERROR_INVAL, 3)); + check(parse("{{\"a\":[\"\\u028\"]}", JSMN_ERROR_INVAL, 4)); + return 0; } int test_partial_string(void) { - int i; - int r; - jsmn_parser p; - jsmntok_t tok[5]; - const char *js = "{\"x\": \"va\\\\ue\", \"y\": \"value y\"}"; - - jsmn_init(&p); - for (i = 1; i <= strlen(js); i++) { - r = jsmn_parse(&p, js, i, tok, sizeof(tok)/sizeof(tok[0])); - if (i == strlen(js)) { - check(r == 5); - check(tokeq(js, tok, 5, - JSMN_OBJECT, -1, -1, 2, - JSMN_STRING, "x", 1, - JSMN_STRING, "va\\\\ue", 0, - JSMN_STRING, "y", 1, - JSMN_STRING, "value y", 0)); - } else { - check(r == JSMN_ERROR_PART); - } - } - return 0; + int r; + unsigned long i; + jsmn_parser p; + jsmntok_t tok[5]; + const char *js = "{\"x\": \"va\\\\ue\", \"y\": \"value y\"}"; + + jsmn_init(&p); + for (i = 1; i <= strlen(js); i++) { + r = jsmn_parse(&p, js, i, tok, sizeof(tok) / sizeof(tok[0])); + if (i == strlen(js)) { + check(r == 5); + check(tokeq(js, tok, 5, JSMN_OBJECT, -1, -1, 2, JSMN_STRING, "x", 1, + JSMN_STRING, "va\\\\ue", 0, JSMN_STRING, "y", 1, JSMN_STRING, + "value y", 0)); + } else { + check(r == JSMN_ERROR_PART); + } + } + return 0; } int test_partial_array(void) { #ifdef JSMN_STRICT - int r; - int i; - jsmn_parser p; - jsmntok_t tok[10]; - const char *js = "[ 1, true, [123, \"hello\"]]"; - - jsmn_init(&p); - for (i = 1; i <= strlen(js); i++) { - r = jsmn_parse(&p, js, i, tok, sizeof(tok)/sizeof(tok[0])); - if (i == strlen(js)) { - check(r == 6); - check(tokeq(js, tok, 6, - JSMN_ARRAY, -1, -1, 3, - JSMN_PRIMITIVE, "1", - JSMN_PRIMITIVE, "true", - JSMN_ARRAY, -1, -1, 2, - JSMN_PRIMITIVE, "123", - JSMN_STRING, "hello", 0)); - } else { - check(r == JSMN_ERROR_PART); - } - } + int r; + unsigned long i; + jsmn_parser p; + jsmntok_t tok[10]; + const char *js = "[ 1, true, [123, \"hello\"]]"; + + jsmn_init(&p); + for (i = 1; i <= strlen(js); i++) { + r = jsmn_parse(&p, js, i, tok, sizeof(tok) / sizeof(tok[0])); + if (i == strlen(js)) { + check(r == 6); + check(tokeq(js, tok, 6, JSMN_ARRAY, -1, -1, 3, JSMN_PRIMITIVE, "1", + JSMN_PRIMITIVE, "true", JSMN_ARRAY, -1, -1, 2, JSMN_PRIMITIVE, + "123", JSMN_STRING, "hello", 0)); + } else { + check(r == JSMN_ERROR_PART); + } + } #endif - return 0; + return 0; } int test_array_nomem(void) { - int i; - int r; - jsmn_parser p; - jsmntok_t toksmall[10], toklarge[10]; - const char *js; - - js = " [ 1, true, [123, \"hello\"]]"; - - for (i = 0; i < 6; i++) { - jsmn_init(&p); - memset(toksmall, 0, sizeof(toksmall)); - memset(toklarge, 0, sizeof(toklarge)); - r = jsmn_parse(&p, js, strlen(js), toksmall, i); - check(r == JSMN_ERROR_NOMEM); - - memcpy(toklarge, toksmall, sizeof(toksmall)); - - r = jsmn_parse(&p, js, strlen(js), toklarge, 10); - check(r >= 0); - check(tokeq(js, toklarge, 4, - JSMN_ARRAY, -1, -1, 3, - JSMN_PRIMITIVE, "1", - JSMN_PRIMITIVE, "true", - JSMN_ARRAY, -1, -1, 2, - JSMN_PRIMITIVE, "123", - JSMN_STRING, "hello", 0)); - } - return 0; + int i; + int r; + jsmn_parser p; + jsmntok_t toksmall[10], toklarge[10]; + const char *js; + + js = " [ 1, true, [123, \"hello\"]]"; + + for (i = 0; i < 6; i++) { + jsmn_init(&p); + memset(toksmall, 0, sizeof(toksmall)); + memset(toklarge, 0, sizeof(toklarge)); + r = jsmn_parse(&p, js, strlen(js), toksmall, i); + check(r == JSMN_ERROR_NOMEM); + + memcpy(toklarge, toksmall, sizeof(toksmall)); + + r = jsmn_parse(&p, js, strlen(js), toklarge, 10); + check(r >= 0); + check(tokeq(js, toklarge, 4, JSMN_ARRAY, -1, -1, 3, JSMN_PRIMITIVE, "1", + JSMN_PRIMITIVE, "true", JSMN_ARRAY, -1, -1, 2, JSMN_PRIMITIVE, + "123", JSMN_STRING, "hello", 0)); + } + return 0; } int test_unquoted_keys(void) { #ifndef JSMN_STRICT - int r; - jsmn_parser p; - jsmntok_t tok[10]; - const char *js; - - jsmn_init(&p); - js = "key1: \"value\"\nkey2 : 123"; - - r = jsmn_parse(&p, js, strlen(js), tok, 10); - check(r >= 0); - check(tokeq(js, tok, 4, - JSMN_PRIMITIVE, "key1", - JSMN_STRING, "value", 0, - JSMN_PRIMITIVE, "key2", - JSMN_PRIMITIVE, "123")); + int r; + jsmn_parser p; + jsmntok_t tok[10]; + const char *js; + + jsmn_init(&p); + js = "key1: \"value\"\nkey2 : 123"; + + r = jsmn_parse(&p, js, strlen(js), tok, 10); + check(r >= 0); + check(tokeq(js, tok, 4, JSMN_PRIMITIVE, "key1", JSMN_STRING, "value", 0, + JSMN_PRIMITIVE, "key2", JSMN_PRIMITIVE, "123")); #endif - return 0; + return 0; } int test_issue_22(void) { - int r; - jsmn_parser p; - jsmntok_t tokens[128]; - const char *js; - - js = "{ \"height\":10, \"layers\":[ { \"data\":[6,6], \"height\":10, " - "\"name\":\"Calque de Tile 1\", \"opacity\":1, \"type\":\"tilelayer\", " - "\"visible\":true, \"width\":10, \"x\":0, \"y\":0 }], " - "\"orientation\":\"orthogonal\", \"properties\": { }, \"tileheight\":32, " - "\"tilesets\":[ { \"firstgid\":1, \"image\":\"..\\/images\\/tiles.png\", " - "\"imageheight\":64, \"imagewidth\":160, \"margin\":0, \"name\":\"Tiles\", " - "\"properties\":{}, \"spacing\":0, \"tileheight\":32, \"tilewidth\":32 }], " - "\"tilewidth\":32, \"version\":1, \"width\":10 }"; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), tokens, 128); - check(r >= 0); - return 0; + int r; + jsmn_parser p; + jsmntok_t tokens[128]; + const char *js; + + js = + "{ \"height\":10, \"layers\":[ { \"data\":[6,6], \"height\":10, " + "\"name\":\"Calque de Tile 1\", \"opacity\":1, \"type\":\"tilelayer\", " + "\"visible\":true, \"width\":10, \"x\":0, \"y\":0 }], " + "\"orientation\":\"orthogonal\", \"properties\": { }, \"tileheight\":32, " + "\"tilesets\":[ { \"firstgid\":1, \"image\":\"..\\/images\\/tiles.png\", " + "\"imageheight\":64, \"imagewidth\":160, \"margin\":0, " + "\"name\":\"Tiles\", " + "\"properties\":{}, \"spacing\":0, \"tileheight\":32, \"tilewidth\":32 " + "}], " + "\"tilewidth\":32, \"version\":1, \"width\":10 }"; + jsmn_init(&p); + r = jsmn_parse(&p, js, strlen(js), tokens, 128); + check(r >= 0); + return 0; } int test_issue_27(void) { - const char *js = - "{ \"name\" : \"Jack\", \"age\" : 27 } { \"name\" : \"Anna\", "; - check(parse(js, JSMN_ERROR_PART, 8)); - return 0; + const char *js = + "{ \"name\" : \"Jack\", \"age\" : 27 } { \"name\" : \"Anna\", "; + check(parse(js, JSMN_ERROR_PART, 8)); + return 0; } int test_input_length(void) { - const char *js; - int r; - jsmn_parser p; - jsmntok_t tokens[10]; - - js = "{\"a\": 0}garbage"; - - jsmn_init(&p); - r = jsmn_parse(&p, js, 8, tokens, 10); - check(r == 3); - check(tokeq(js, tokens, 3, - JSMN_OBJECT, -1, -1, 1, - JSMN_STRING, "a", 1, - JSMN_PRIMITIVE, "0")); - return 0; + const char *js; + int r; + jsmn_parser p; + jsmntok_t tokens[10]; + + js = "{\"a\": 0}garbage"; + + jsmn_init(&p); + r = jsmn_parse(&p, js, 8, tokens, 10); + check(r == 3); + check(tokeq(js, tokens, 3, JSMN_OBJECT, -1, -1, 1, JSMN_STRING, "a", 1, + JSMN_PRIMITIVE, "0")); + return 0; } int test_count(void) { - jsmn_parser p; - const char *js; + jsmn_parser p; + const char *js; - js = "{}"; - jsmn_init(&p); - check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 1); + js = "{}"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 1); - js = "[]"; - jsmn_init(&p); - check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 1); + js = "[]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 1); - js = "[[]]"; - jsmn_init(&p); - check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 2); + js = "[[]]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 2); - js = "[[], []]"; - jsmn_init(&p); - check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 3); + js = "[[], []]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 3); - js = "[[], []]"; - jsmn_init(&p); - check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 3); + js = "[[], []]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 3); - js = "[[], [[]], [[], []]]"; - jsmn_init(&p); - check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 7); + js = "[[], [[]], [[], []]]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 7); - js = "[\"a\", [[], []]]"; - jsmn_init(&p); - check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 5); + js = "[\"a\", [[], []]]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 5); - js = "[[], \"[], [[]]\", [[]]]"; - jsmn_init(&p); - check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 5); + js = "[[], \"[], [[]]\", [[]]]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 5); - js = "[1, 2, 3]"; - jsmn_init(&p); - check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 4); + js = "[1, 2, 3]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 4); - js = "[1, 2, [3, \"a\"], null]"; - jsmn_init(&p); - check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 7); + js = "[1, 2, [3, \"a\"], null]"; + jsmn_init(&p); + check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 7); - return 0; + return 0; } - int test_nonstrict(void) { #ifndef JSMN_STRICT - const char *js; - js = "a: 0garbage"; - check(parse(js, 2, 2, - JSMN_PRIMITIVE, "a", - JSMN_PRIMITIVE, "0garbage")); - - js = "Day : 26\nMonth : Sep\n\nYear: 12"; - check(parse(js, 6, 6, - JSMN_PRIMITIVE, "Day", - JSMN_PRIMITIVE, "26", - JSMN_PRIMITIVE, "Month", - JSMN_PRIMITIVE, "Sep", - JSMN_PRIMITIVE, "Year", - JSMN_PRIMITIVE, "12")); - - //nested {s don't cause a parse error. - js = "\"key {1\": 1234"; - check(parse(js, 2, 2, - JSMN_STRING, "key {1", 1, - JSMN_PRIMITIVE, "1234")); + const char *js; + js = "a: 0garbage"; + check(parse(js, 2, 2, JSMN_PRIMITIVE, "a", JSMN_PRIMITIVE, "0garbage")); + js = "Day : 26\nMonth : Sep\n\nYear: 12"; + check(parse(js, 6, 6, JSMN_PRIMITIVE, "Day", JSMN_PRIMITIVE, "26", + JSMN_PRIMITIVE, "Month", JSMN_PRIMITIVE, "Sep", JSMN_PRIMITIVE, + "Year", JSMN_PRIMITIVE, "12")); + + /* nested {s don't cause a parse error. */ + js = "\"key {1\": 1234"; + check(parse(js, 2, 2, JSMN_STRING, "key {1", 1, JSMN_PRIMITIVE, "1234")); #endif - return 0; + return 0; } int test_unmatched_brackets(void) { - const char *js; - js = "\"key 1\": 1234}"; - check(parse(js, JSMN_ERROR_INVAL, 2)); - js = "{\"key 1\": 1234"; - check(parse(js, JSMN_ERROR_PART, 3)); - js = "{\"key 1\": 1234}}"; - check(parse(js, JSMN_ERROR_INVAL, 3)); - js = "\"key 1\"}: 1234"; - check(parse(js, JSMN_ERROR_INVAL, 3)); - js = "{\"key {1\": 1234}"; - check(parse(js, 3, 3, - JSMN_OBJECT, 0, 16, 1, - JSMN_STRING, "key {1", 1, - JSMN_PRIMITIVE, "1234")); - js = "{{\"key 1\": 1234}"; - check(parse(js, JSMN_ERROR_PART, 4)); - return 0; + const char *js; + js = "\"key 1\": 1234}"; + check(parse(js, JSMN_ERROR_INVAL, 2)); + js = "{\"key 1\": 1234"; + check(parse(js, JSMN_ERROR_PART, 3)); + js = "{\"key 1\": 1234}}"; + check(parse(js, JSMN_ERROR_INVAL, 3)); + js = "\"key 1\"}: 1234"; + check(parse(js, JSMN_ERROR_INVAL, 3)); + js = "{\"key {1\": 1234}"; + check(parse(js, 3, 3, JSMN_OBJECT, 0, 16, 1, JSMN_STRING, "key {1", 1, + JSMN_PRIMITIVE, "1234")); + js = "{\"key 1\":{\"key 2\": 1234}"; + check(parse(js, JSMN_ERROR_PART, 5)); + return 0; +} + +int test_object_key(void) { + const char *js; + + js = "{\"key\": 1}"; + check(parse(js, 3, 3, JSMN_OBJECT, 0, 10, 1, JSMN_STRING, "key", 1, + JSMN_PRIMITIVE, "1")); +#ifdef JSMN_STRICT + js = "{true: 1}"; + check(parse(js, JSMN_ERROR_INVAL, 3)); + js = "{1: 1}"; + check(parse(js, JSMN_ERROR_INVAL, 3)); + js = "{{\"key\": 1}: 2}"; + check(parse(js, JSMN_ERROR_INVAL, 5)); + js = "{[1,2]: 2}"; + check(parse(js, JSMN_ERROR_INVAL, 5)); +#endif + return 0; } int main(void) { - test(test_empty, "test for a empty JSON objects/arrays"); - test(test_object, "test for a JSON objects"); - test(test_array, "test for a JSON arrays"); - test(test_primitive, "test primitive JSON data types"); - test(test_string, "test string JSON data types"); - - test(test_partial_string, "test partial JSON string parsing"); - test(test_partial_array, "test partial array reading"); - test(test_array_nomem, "test array reading with a smaller number of tokens"); - test(test_unquoted_keys, "test unquoted keys (like in JavaScript)"); - test(test_input_length, "test strings that are not null-terminated"); - test(test_issue_22, "test issue #22"); - test(test_issue_27, "test issue #27"); - test(test_count, "test tokens count estimation"); - test(test_nonstrict, "test for non-strict mode"); - test(test_unmatched_brackets, "test for unmatched brackets"); - printf("\nPASSED: %d\nFAILED: %d\n", test_passed, test_failed); - return (test_failed > 0); + test(test_empty, "test for a empty JSON objects/arrays"); + test(test_object, "test for a JSON objects"); + test(test_array, "test for a JSON arrays"); + test(test_primitive, "test primitive JSON data types"); + test(test_string, "test string JSON data types"); + + test(test_partial_string, "test partial JSON string parsing"); + test(test_partial_array, "test partial array reading"); + test(test_array_nomem, "test array reading with a smaller number of tokens"); + test(test_unquoted_keys, "test unquoted keys (like in JavaScript)"); + test(test_input_length, "test strings that are not null-terminated"); + test(test_issue_22, "test issue #22"); + test(test_issue_27, "test issue #27"); + test(test_count, "test tokens count estimation"); + test(test_nonstrict, "test for non-strict mode"); + test(test_unmatched_brackets, "test for unmatched brackets"); + test(test_object_key, "test for key type"); + printf("\nPASSED: %d\nFAILED: %d\n", test_passed, test_failed); + return (test_failed > 0); } diff --git a/test/testutil.h b/test/testutil.h index 9a1eb2d65..b4a51b53d 100644 --- a/test/testutil.h +++ b/test/testutil.h @@ -1,94 +1,96 @@ #ifndef __TEST_UTIL_H__ #define __TEST_UTIL_H__ -#include "../jsmn.c" +#include "../jsmn.h" -static int vtokeq(const char *s, jsmntok_t *t, int numtok, va_list ap) { - if (numtok > 0) { - int i, start, end, size; - int type; - char *value; +static int vtokeq(const char *s, jsmntok_t *t, unsigned long numtok, + va_list ap) { + if (numtok > 0) { + unsigned long i; + int start, end, size; + int type; + char *value; - size = -1; - value = NULL; - for (i = 0; i < numtok; i++) { - type = va_arg(ap, int); - if (type == JSMN_STRING) { - value = va_arg(ap, char *); - size = va_arg(ap, int); - start = end = -1; - } else if (type == JSMN_PRIMITIVE) { - value = va_arg(ap, char *); - start = end = size = -1; - } else { - start = va_arg(ap, int); - end = va_arg(ap, int); - size = va_arg(ap, int); - value = NULL; - } - if (t[i].type != type) { - printf("token %d type is %d, not %d\n", i, t[i].type, type); - return 0; - } - if (start != -1 && end != -1) { - if (t[i].start != start) { - printf("token %d start is %d, not %d\n", i, t[i].start, start); - return 0; - } - if (t[i].end != end ) { - printf("token %d end is %d, not %d\n", i, t[i].end, end); - return 0; - } - } - if (size != -1 && t[i].size != size) { - printf("token %d size is %d, not %d\n", i, t[i].size, size); - return 0; - } + size = -1; + value = NULL; + for (i = 0; i < numtok; i++) { + type = va_arg(ap, int); + if (type == JSMN_STRING) { + value = va_arg(ap, char *); + size = va_arg(ap, int); + start = end = -1; + } else if (type == JSMN_PRIMITIVE) { + value = va_arg(ap, char *); + start = end = size = -1; + } else { + start = va_arg(ap, int); + end = va_arg(ap, int); + size = va_arg(ap, int); + value = NULL; + } + if (t[i].type != type) { + printf("token %lu type is %d, not %d\n", i, t[i].type, type); + return 0; + } + if (start != -1 && end != -1) { + if (t[i].start != start) { + printf("token %lu start is %d, not %d\n", i, t[i].start, start); + return 0; + } + if (t[i].end != end) { + printf("token %lu end is %d, not %d\n", i, t[i].end, end); + return 0; + } + } + if (size != -1 && t[i].size != size) { + printf("token %lu size is %d, not %d\n", i, t[i].size, size); + return 0; + } - if (s != NULL && value != NULL) { - const char *p = s + t[i].start; - if (strlen(value) != t[i].end - t[i].start || - strncmp(p, value, t[i].end - t[i].start) != 0) { - printf("token %d value is %.*s, not %s\n", i, t[i].end-t[i].start, - s+t[i].start, value); - return 0; - } - } - } - } - return 1; + if (s != NULL && value != NULL) { + const char *p = s + t[i].start; + if (strlen(value) != (unsigned long)(t[i].end - t[i].start) || + strncmp(p, value, t[i].end - t[i].start) != 0) { + printf("token %lu value is %.*s, not %s\n", i, t[i].end - t[i].start, + s + t[i].start, value); + return 0; + } + } + } + } + return 1; } static int tokeq(const char *s, jsmntok_t *tokens, int numtok, ...) { - int ok; - va_list args; - va_start(args, numtok); - ok = vtokeq(s, tokens, numtok, args); - va_end(args); - return ok; + int ok; + va_list args; + va_start(args, numtok); + ok = vtokeq(s, tokens, numtok, args); + va_end(args); + return ok; } static int parse(const char *s, int status, int numtok, ...) { - int r; - int ok = 1; - va_list args; - jsmn_parser p; - jsmntok_t *t = malloc(numtok * sizeof(jsmntok_t)); + int r; + int ok = 1; + va_list args; + jsmn_parser p; + jsmntok_t *t = malloc(numtok * sizeof(jsmntok_t)); - jsmn_init(&p); - r = jsmn_parse(&p, s, strlen(s), t, numtok); - if (r != status) { - printf("status is %d, not %d\n", r, status); - return 0; - } + jsmn_init(&p); + r = jsmn_parse(&p, s, strlen(s), t, numtok); + if (r != status) { + printf("status is %d, not %d\n", r, status); + return 0; + } - if (status >= 0) { - va_start(args, numtok); - ok = vtokeq(s, t, numtok, args); - va_end(args); - } - free(t); - return ok; + if (status >= 0) { + va_start(args, numtok); + ok = vtokeq(s, t, numtok, args); + va_end(args); + } + free(t); + return ok; } #endif /* __TEST_UTIL_H__ */ -- cgit v1.2.3 From cdcfaafa49ffe5661978292a55cec7fd459571e4 Mon Sep 17 00:00:00 2001 From: Sanjeev Gupta Date: Sat, 13 Jul 2019 22:01:56 +0800 Subject: Quieten a warning from the compiler gcc (and others) like a default case for switch statement, even if it is empty. --- jsmn.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jsmn.h b/jsmn.h index b95368a20..cb27ca112 100644 --- a/jsmn.h +++ b/jsmn.h @@ -154,6 +154,9 @@ static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, case ']': case '}': goto found; + default: + /* to quiet a warning from gcc*/ + break; } if (js[parser->pos] < 32 || js[parser->pos] >= 127) { parser->pos = start; -- cgit v1.2.3 From 0837288b7c6dbd3c015f6a184cfa1e99937c5d09 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 8 Nov 2019 16:52:31 +0100 Subject: jsmn: declare struct names to allow forward decls Both `jsmntok_t` and `jsmn_parser` are declared as anonymous structures that are typedeffed to their actual name. This forces all downstream users of jsmn to always use the typedef name, instead of using e.g. `struct jsmn_parser`. While this might be considered a matter of taste, using typedefs only has the technical downside of disallowing forward declarations. E.g. if a dependent whishes to declare a pointer to `jsmntok_t` without actually pulling in the "jsmn.h" header, then he is not able to do so because there is no way in C to provide a forward declaration for typedefs to anonymous structs. Fix this by providing names for both `jsmntok_t` and `jsmn_parser` structures. --- jsmn.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jsmn.h b/jsmn.h index cb27ca112..3178dcc97 100644 --- a/jsmn.h +++ b/jsmn.h @@ -66,7 +66,7 @@ enum jsmnerr { * start start position in JSON data string * end end position in JSON data string */ -typedef struct { +typedef struct jsmntok { jsmntype_t type; int start; int end; @@ -80,7 +80,7 @@ typedef struct { * JSON parser. Contains an array of token blocks available. Also stores * the string being parsed now and current position in that string. */ -typedef struct { +typedef struct jsmn_parser { unsigned int pos; /* offset in the JSON string */ unsigned int toknext; /* next token to allocate */ int toksuper; /* superior token node, e.g. parent object or array */ -- cgit v1.2.3 From 7b6858a5855299d173c5ab2b46e611bf9961cbef Mon Sep 17 00:00:00 2001 From: Alexey Radkov Date: Wed, 19 Feb 2020 17:32:48 +0300 Subject: Fixed a typo (value -> number) (#186) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0f6ed27ab..f8249f3dd 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ the `js` string. A non-negative return value of `jsmn_parse` is the number of tokens actually used by the parser. Passing NULL instead of the tokens array would not store parsing results, but -instead the function will return the value of tokens needed to parse the given +instead the function will return the number of tokens needed to parse the given string. This can be useful if you don't know yet how many tokens to allocate. If something goes wrong, you will get an error. Error will be one of these: -- cgit v1.2.3 From a91022a07d70674fc4b8c5e3f448f2bd93b00066 Mon Sep 17 00:00:00 2001 From: Toni Uhlig Date: Fri, 13 Mar 2020 21:46:40 +0100 Subject: fix gcc/clang warning and unnecessary implicit type conversion to different size/signedness (#187) * fixed gcc/clang warnings regarding implicit numeric to enum type conversion * fixed unnecessary implicit type casts regarding size and signedness Co-authored-by: Toni Uhlig --- test/testutil.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/testutil.h b/test/testutil.h index b4a51b53d..bdee13934 100644 --- a/test/testutil.h +++ b/test/testutil.h @@ -8,13 +8,13 @@ static int vtokeq(const char *s, jsmntok_t *t, unsigned long numtok, if (numtok > 0) { unsigned long i; int start, end, size; - int type; + jsmntype_t type; char *value; size = -1; value = NULL; for (i = 0; i < numtok; i++) { - type = va_arg(ap, int); + type = va_arg(ap, jsmntype_t); if (type == JSMN_STRING) { value = va_arg(ap, char *); size = va_arg(ap, int); @@ -61,7 +61,7 @@ static int vtokeq(const char *s, jsmntok_t *t, unsigned long numtok, return 1; } -static int tokeq(const char *s, jsmntok_t *tokens, int numtok, ...) { +static int tokeq(const char *s, jsmntok_t *tokens, unsigned long numtok, ...) { int ok; va_list args; va_start(args, numtok); @@ -70,7 +70,7 @@ static int tokeq(const char *s, jsmntok_t *tokens, int numtok, ...) { return ok; } -static int parse(const char *s, int status, int numtok, ...) { +static int parse(const char *s, int status, unsigned long numtok, ...) { int r; int ok = 1; va_list args; -- cgit v1.2.3