diff --git a/Makefile b/Makefile index dcbdd89d..9ff174e3 100644 --- a/Makefile +++ b/Makefile @@ -31,6 +31,7 @@ clean: rm -f *.o example/*.o rm -f simple_example rm -f jsondump + rm -f test/test_default test/test_strict test/test_links test/test_strict_links .PHONY: clean test diff --git a/jsmn.h b/jsmn.h index 3178dcc9..4e1ebbb7 100644 --- a/jsmn.h +++ b/jsmn.h @@ -1,4 +1,5 @@ -/* +/* vim: set expandtab ts=8 sts=2 sw=2: + * * MIT License * * Copyright (c) 2010 Serge Zaitsev @@ -68,14 +69,50 @@ enum jsmnerr { */ typedef struct jsmntok { jsmntype_t type; - int start; - int end; - int size; + unsigned int start; + unsigned int end; + unsigned int size; #ifdef JSMN_PARENT_LINKS int parent; #endif } jsmntok_t; +/** + * JSON parser state. Describes what kind of token or character is expected + * next. + */ +typedef enum { + JSMN_DELIMITER = 0x1, /* Expecting colon if JSMN_KEY, comma otherwise */ + JSMN_CAN_CLOSE = 0x2, /* Object or array can close here */ + + JSMN_KEY = 0x10, /* Expecting an object key or the delimiter following */ + JSMN_VALUE = 0x20, /* Expecting an object value or the delimiter following */ + JSMN_IN_ARRAY = 0x40, /* Expecting array item or the delimiter following */ + /* In an object if (state & JSMN_IN_OBJECT). Otherwise, in an array or at + * the root. */ + JSMN_IN_OBJECT = JSMN_KEY | JSMN_VALUE, + + /* Actual values for parser->state */ + /* Root: not in any array or object */ + JSMN_STATE_ROOT = 0x0, + /* Just saw object opening ({). Expecting key or close. */ + JSMN_STATE_OBJ_NEW = (JSMN_KEY | JSMN_CAN_CLOSE), + /* Just saw a comma in an object. Expecting key. */ + JSMN_STATE_OBJ_KEY = (JSMN_KEY), + /* Just saw a key in an object. Expecting colon. */ + JSMN_STATE_OBJ_COLON = (JSMN_KEY | JSMN_DELIMITER), + /* Just saw a colon in an object. Expecting value. */ + JSMN_STATE_OBJ_VAL = (JSMN_VALUE), + /* Just saw a value in an object. Expecting comma or close. */ + JSMN_STATE_OBJ_COMMA = (JSMN_VALUE | JSMN_DELIMITER | JSMN_CAN_CLOSE), + /* Just saw an array opening ([). Expecting item or close. */ + JSMN_STATE_ARRAY_NEW = (JSMN_IN_ARRAY | JSMN_CAN_CLOSE), + /* Just saw a comma in an array. Expecting item. */ + JSMN_STATE_ARRAY_ITEM = (JSMN_IN_ARRAY), + /* Just saw an item in an array. Expecting comma or close. */ + JSMN_STATE_ARRAY_COMMA = (JSMN_DELIMITER | JSMN_IN_ARRAY | JSMN_CAN_CLOSE) +} jsmnstate_t; + /** * JSON parser. Contains an array of token blocks available. Also stores * the string being parsed now and current position in that string. @@ -84,6 +121,9 @@ 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 */ + jsmnstate_t state; /* parser state, from jsmnstate_t */ + int tokbefore; /* token immediately preceding the first token in the + current JSON object */ } jsmn_parser; /** @@ -110,10 +150,10 @@ static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens, return NULL; } tok = &tokens[parser->toknext++]; - tok->start = tok->end = -1; + tok->end = 0; tok->size = 0; #ifdef JSMN_PARENT_LINKS - tok->parent = -1; + tok->parent = parser->tokbefore; #endif return tok; } @@ -126,36 +166,219 @@ static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type, token->type = type; token->start = start; token->end = end; - token->size = 0; } +#ifdef JSMN_STRICT +static const char true_str[] = "true"; +static const char false_str[] = "false"; +static const char null_str[] = "null"; + +typedef enum { + JSMN_NUM_INT_START, + JSMN_NUM_INT_HAVE_SIGN, + JSMN_NUM_INT_ZERO, + JSMN_NUM_INT, + JSMN_NUM_FRAC_START, + JSMN_NUM_FRAC, + JSMN_NUM_EXP_START, + JSMN_NUM_EXP_HAVE_SIGN, + JSMN_NUM_EXP +} jsmn_numberstate; +#endif + /** - * Fills next available token with JSON primitive. + * If extend is false, fills next available token with JSON primitive. If + * extend is true, complete the primitive that the parser is in the middle of. + * If extend is false, it is assumed that the current char is a valid start of + * a primitive. */ static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, const size_t len, jsmntok_t *tokens, - const size_t num_tokens) { + const size_t num_tokens, int extend) { jsmntok_t *token; int start; - +#ifdef JSMN_STRICT + int i; + if (extend) { + parser->pos = tokens[parser->toknext - 1].start; + } +#endif start = parser->pos; - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { +#ifdef JSMN_STRICT + if (js[parser->pos] == 't') { + for (i = 1; + i < sizeof(true_str) - 1; + i++) { + if (parser->pos + i >= len || js[parser->pos + i] == '\0') { + return JSMN_ERROR_PART; + } else if (js[parser->pos + i] != true_str[i]) { + return JSMN_ERROR_INVAL; + } + } + parser->pos += 4; + } else if (js[parser->pos] == 'f') { + for (i = 1; + i < sizeof(false_str) - 1; + i++) { + if (parser->pos + i >= len || js[parser->pos + i] == '\0') { + return JSMN_ERROR_PART; + } else if (js[parser->pos + i] != false_str[i]) { + return JSMN_ERROR_INVAL; + } + } + parser->pos += 5; + } else if (js[parser->pos] == 'n') { + for (i = 1; + i < sizeof(null_str) - 1; + i++) { + if (parser->pos + i >= len || js[parser->pos + i] == '\0') { + return JSMN_ERROR_PART; + } else if (js[parser->pos + i] != null_str[i]) { + return JSMN_ERROR_INVAL; + } + } + parser->pos += 4; + } else { + jsmn_numberstate numstate = JSMN_NUM_INT_START; + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + switch (js[parser->pos]) { + case '-': + if (numstate == JSMN_NUM_INT_START || + numstate == JSMN_NUM_EXP_START) { + numstate++; + } else { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + break; + case '0': + if (numstate == JSMN_NUM_INT_START || + numstate == JSMN_NUM_INT_HAVE_SIGN) { + numstate = JSMN_NUM_INT_ZERO; + } else if (numstate == JSMN_NUM_INT) { + numstate = JSMN_NUM_INT; + } else if (numstate == JSMN_NUM_FRAC || + numstate == JSMN_NUM_FRAC_START) { + numstate = JSMN_NUM_FRAC; + } else if (numstate == JSMN_NUM_EXP || + numstate == JSMN_NUM_EXP_START || + numstate == JSMN_NUM_EXP_HAVE_SIGN) { + numstate = JSMN_NUM_EXP; + } else { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + break; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (numstate == JSMN_NUM_INT || + numstate == JSMN_NUM_INT_START || + numstate == JSMN_NUM_INT_HAVE_SIGN) { + numstate = JSMN_NUM_INT; + } else if (numstate == JSMN_NUM_FRAC || + numstate == JSMN_NUM_FRAC_START) { + numstate = JSMN_NUM_FRAC; + } else if (numstate == JSMN_NUM_EXP || + numstate == JSMN_NUM_EXP_START || + numstate == JSMN_NUM_EXP_HAVE_SIGN) { + numstate = JSMN_NUM_EXP; + } else { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + break; + case '+': + if (numstate == JSMN_NUM_EXP_START) { + numstate++; + } else { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + break; + case 'e': + case 'E': + if (numstate == JSMN_NUM_INT_ZERO || + numstate == JSMN_NUM_INT || + numstate == JSMN_NUM_FRAC) { + numstate = JSMN_NUM_EXP_START; + } else { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + break; + case '.': + if (numstate == JSMN_NUM_INT_ZERO || + numstate == JSMN_NUM_INT) { + numstate = JSMN_NUM_FRAC_START; + } else { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + break; + default: + goto check_number_complete; + } + } +check_number_complete: + if (numstate != JSMN_NUM_INT && + numstate != JSMN_NUM_INT_ZERO && + numstate != JSMN_NUM_FRAC && + numstate != JSMN_NUM_EXP) { + if (parser->pos >= len || js[parser->pos] == '\0') { + parser->pos = start; + return JSMN_ERROR_PART; + } else { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } + } + + /* Verify that what comes after the primitive is a non-primitive character */ + if (parser->pos < len) { 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 '"': + case '[': + case ']': + case '{': + case '}': + case '\0': + break; + default: + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } +#else + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + switch (js[parser->pos]) { case '\t': case '\r': case '\n': case ' ': case ',': + case ':': + case '"': + case '[': case ']': + case '{': case '}': goto found; - default: - /* to quiet a warning from gcc*/ + default: /* to quiet a warning from gcc*/ break; } if (js[parser->pos] < 32 || js[parser->pos] >= 127) { @@ -163,10 +386,6 @@ static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, 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: @@ -174,16 +393,17 @@ static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, parser->pos--; return 0; } - token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) { - parser->pos = start; - return JSMN_ERROR_NOMEM; + if (extend) { + tokens[parser->toknext - 1].end = parser->pos; + } else { + 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); + parser->pos--; } - jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); -#ifdef JSMN_PARENT_LINKS - token->parent = parser->toksuper; -#endif - parser->pos--; return 0; } @@ -194,12 +414,11 @@ 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; + unsigned int start = parser->pos; - int start = parser->pos; - + /* Skip starting quote */ parser->pos++; - /* Skip starting quote */ for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { char c = js[parser->pos]; @@ -214,9 +433,6 @@ static int 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 0; } @@ -224,6 +440,7 @@ static int jsmn_parse_string(jsmn_parser *parser, const char *js, if (c == '\\' && parser->pos + 1 < len) { int i; parser->pos++; +#ifdef JSMN_STRICT switch (js[parser->pos]) { /* Allowed escaped symbols */ case '\"': @@ -256,25 +473,62 @@ static int jsmn_parse_string(jsmn_parser *parser, const char *js, parser->pos = start; return JSMN_ERROR_INVAL; } +#endif + } + +#ifdef JSMN_STRICT + /* No control characters allowed in strict mode */ + if (c >= 0 && c < 32) { + parser->pos = start; + return JSMN_ERROR_INVAL; } +#endif } parser->pos = start; return JSMN_ERROR_PART; } +/** + * If in the middle of a primitive, add the rest of it to the current primitive + * token + */ +static int jsmn_finish_primitive(jsmn_parser *parser, const char *js, + const size_t len, jsmntok_t *tokens, + const size_t num_tokens) { + unsigned int pos = parser->pos; + if (parser->toknext > 0 && parser->pos < len) { + if (js[pos] > 32 && js[pos] < 127 && js[pos] != ',' && js[pos] != ':' && + js[pos] != '{' && js[pos] != '}' && js[pos] != '[' && js[pos] != ']' && + js[pos] != '"' && js[pos - 1] > 32 && js[pos - 1] < 127 && + js[pos - 1] != ',' && js[pos - 1] != ':' && js[pos - 1] != '{' && + js[pos - 1] != '}' && js[pos - 1] != '[' && js[pos - 1] != ']' && + js[pos - 1] != '"') { + return jsmn_parse_primitive(parser, js, len, tokens, num_tokens, 1); + } + } + return 0; +} + /** * 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; +#ifndef JSMN_PARENT_LINKS int i; +#endif jsmntok_t *token; + int depth = 0; /* Obj/array nesting depth. Used only when tokens == NULL */ int count = parser->toknext; + r = jsmn_finish_primitive(parser, js, len, tokens, num_tokens); + if (r < 0) { + return r; + } + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { char c; - jsmntype_t type; c = js[parser->pos]; switch (c) { @@ -282,90 +536,112 @@ JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, case '[': count++; if (tokens == NULL) { + depth++; break; } + if (parser->state & JSMN_KEY) { + return JSMN_ERROR_INVAL; + } 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++; + if (parser->toksuper != parser->tokbefore) { + 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; + token->start = parser->pos; + if (c == '{') { + token->type = JSMN_OBJECT; + parser->state = JSMN_STATE_OBJ_NEW; + } else { + token->type = JSMN_ARRAY; + parser->state = JSMN_STATE_ARRAY_NEW; + } break; case '}': - case ']': if (tokens == NULL) { + depth--; break; - } - type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); -#ifdef JSMN_PARENT_LINKS - if (parser->toknext < 1) { + } else if (!(parser->state & JSMN_IN_OBJECT) || + !(parser->state & JSMN_CAN_CLOSE)) { 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]; - } + if (parser->state & JSMN_VALUE) { +#ifdef JSMN_PARENT_LINKS + parser->toksuper = tokens[parser->toksuper].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; + for (i = parser->toksuper - 1; i != parser->tokbefore; i--) { + if (tokens[i].end == 0) { + break; } - parser->toksuper = -1; - token->end = parser->pos + 1; - break; } + parser->toksuper = i; +#endif } - /* Error if unmatched closing bracket */ - if (i == -1) { + goto container_close; + case ']': + if (tokens == NULL) { + depth--; + break; + } else if (!(parser->state & JSMN_IN_ARRAY) || + !(parser->state & JSMN_CAN_CLOSE)) { return JSMN_ERROR_INVAL; } - for (; i >= 0; i--) { - token = &tokens[i]; - if (token->start != -1 && token->end == -1) { - parser->toksuper = i; - break; +container_close: + token = &tokens[parser->toksuper]; + token->end = parser->pos + 1; +#ifdef JSMN_PARENT_LINKS + parser->toksuper = token->parent; +#else + if (parser->toksuper - 1 == parser->tokbefore || token[-1].size > 0) { + parser->toksuper--; + } else { + for (i = parser->toksuper - 2; i != parser->tokbefore; i--) { + if (tokens[i].end == 0) { + break; + } } + parser->toksuper = i; } #endif + if (parser->toksuper == parser->tokbefore) { + parser->tokbefore = parser->toknext - 1; + parser->toksuper = parser->toknext - 1; + parser->state = JSMN_STATE_ROOT; + } else { + parser->state = (tokens[parser->toksuper].type == JSMN_ARRAY) ? + JSMN_STATE_ARRAY_COMMA : JSMN_STATE_OBJ_COMMA; + } break; case '\"': + count++; 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++; + if (tokens == NULL) { + break; + } + if (parser->toksuper == parser->tokbefore) { + parser->tokbefore = parser->toknext - 1; + parser->toksuper = parser->toknext - 1; + parser->state = JSMN_STATE_ROOT; + break; + } else if (parser->state & JSMN_DELIMITER) { + return JSMN_ERROR_INVAL; + } + tokens[parser->toksuper].size++; +#ifdef JSMN_PARENT_LINKS + tokens[parser->toknext - 1].parent = parser->toksuper; +#endif + if (parser->state & JSMN_KEY) { + parser->state = JSMN_STATE_OBJ_COLON; + } else { + parser->state |= JSMN_DELIMITER | JSMN_CAN_CLOSE; } break; case '\t': @@ -374,24 +650,29 @@ JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, case ' ': break; case ':': + if (tokens == NULL) { + break; + } else if (parser->state != JSMN_STATE_OBJ_COLON) { + return JSMN_ERROR_INVAL; + } parser->toksuper = parser->toknext - 1; + parser->state = JSMN_STATE_OBJ_VAL; break; case ',': - if (tokens != NULL && parser->toksuper != -1 && - tokens[parser->toksuper].type != JSMN_ARRAY && - tokens[parser->toksuper].type != JSMN_OBJECT) { + if (tokens == NULL) { + break; + } else if (parser->state == JSMN_STATE_OBJ_COMMA) { #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; - } - } - } + for (i = parser->toksuper - 1; tokens[i].end != 0; i--); + parser->toksuper = i; #endif + parser->state = JSMN_STATE_OBJ_KEY; + } else if (parser->state == JSMN_STATE_ARRAY_COMMA) { + parser->state = JSMN_STATE_ARRAY_ITEM; + } else { + return JSMN_ERROR_INVAL; } break; #ifdef JSMN_STRICT @@ -410,28 +691,43 @@ JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, 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); + count++; + r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens, 0); if (r < 0) { return r; } - count++; - if (parser->toksuper != -1 && tokens != NULL) { - tokens[parser->toksuper].size++; + if (tokens == NULL) { + break; + } + if (parser->toksuper == parser->tokbefore) { + parser->tokbefore = parser->toknext - 1; + parser->toksuper = parser->toknext - 1; + parser->state = JSMN_STATE_ROOT; + break; +#ifdef JSMN_STRICT + } else if (parser->state & (JSMN_DELIMITER | JSMN_KEY)) { +#else + } else if (parser->state & JSMN_DELIMITER) { +#endif + return JSMN_ERROR_INVAL; + } + tokens[parser->toksuper].size++; +#ifdef JSMN_PARENT_LINKS + tokens[parser->toknext - 1].parent = parser->toksuper; +#endif +#ifdef JSMN_STRICT + parser->state |= JSMN_DELIMITER | JSMN_CAN_CLOSE; +#else + if (parser->state & JSMN_KEY) { + parser->state = JSMN_STATE_OBJ_COLON; + } else { + parser->state |= JSMN_DELIMITER | JSMN_CAN_CLOSE; } +#endif break; - #ifdef JSMN_STRICT /* Unexpected char in strict mode */ default: @@ -440,16 +736,11 @@ JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, } } - 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; - } - } + if (depth == 0 && parser->state == JSMN_STATE_ROOT) { + return count; + } else { + return JSMN_ERROR_PART; } - - return count; } /** @@ -460,6 +751,8 @@ JSMN_API void jsmn_init(jsmn_parser *parser) { parser->pos = 0; parser->toknext = 0; parser->toksuper = -1; + parser->state = JSMN_STATE_ROOT; + parser->tokbefore = -1; } #endif /* JSMN_HEADER */ diff --git a/test/tests.c b/test/tests.c index d8a4d922..4ce90af7 100644 --- a/test/tests.c +++ b/test/tests.c @@ -33,28 +33,34 @@ int test_object(void) { #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\": {2}}", JSMN_ERROR_INVAL, 4)); + check(parse("{\"a\": {2: 3}}", JSMN_ERROR_INVAL, 5)); + check(parse("{\"a\": {\"a\": 2 3}}", JSMN_ERROR_INVAL, 6)); + 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, 3)); + check(parse("{\"a\":\"b\":\"c\"}", JSMN_ERROR_INVAL, 4)); + check(parse("{,}", JSMN_ERROR_INVAL, 1)); + check(parse("{\"a\":}", JSMN_ERROR_INVAL, 2)); + check(parse("{\"a\" \"b\"}", JSMN_ERROR_INVAL, 3)); + check(parse("{\"a\" ::::: \"b\"}", JSMN_ERROR_INVAL, 3)); + check(parse("{\"a\": [1 \"b\"]}", JSMN_ERROR_INVAL, 5)); + check(parse("{\"a\"\"\"}", JSMN_ERROR_INVAL, 3)); + check(parse("{\"a\":1\"\"}", JSMN_ERROR_INVAL, 4)); + check(parse("{\"a\":1\"b\":1}", JSMN_ERROR_INVAL, 5)); + check(parse("{\"a\":\"b\", \"c\":\"d\", {\"e\": \"f\"}}", + JSMN_ERROR_INVAL, 8)); #endif 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}", 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));*/ + check(parse("[\"a\": 1]", JSMN_ERROR_INVAL, 3)); return 0; } @@ -67,8 +73,54 @@ int test_primitive(void) { 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("{\"intVar\" : 0}", 3, 3, JSMN_OBJECT, -1, -1, 1, JSMN_STRING, + "intVar", 1, JSMN_PRIMITIVE, "0")); check(parse("{\"floatVar\" : 12.345}", 3, 3, JSMN_OBJECT, -1, -1, 1, JSMN_STRING, "floatVar", 1, JSMN_PRIMITIVE, "12.345")); + check(parse("{\"floatVar\" : -12.345}", 3, 3, JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "floatVar", 1, JSMN_PRIMITIVE, "-12.345")); + check(parse("{\"floatVar\" : 0.345}", 3, 3, JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "floatVar", 1, JSMN_PRIMITIVE, "0.345")); + check(parse("{\"floatVar\" : 12.345e6}", 3, 3, JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "floatVar", 1, JSMN_PRIMITIVE, "12.345e6")); + check(parse("{\"floatVar\" : 12.345E+6}", 3, 3, JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "floatVar", 1, JSMN_PRIMITIVE, "12.345E+6")); + check(parse("{\"floatVar\" : 12e+6}", 3, 3, JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "floatVar", 1, JSMN_PRIMITIVE, "12e+6")); + +#ifdef JSMN_STRICT + check(parse("{\"boolVar\" : tru }", JSMN_ERROR_INVAL, 3)); + check(parse("{\"boolVar\" : falsee }", JSMN_ERROR_INVAL, 3)); + check(parse("{\"nullVar\" : nulm }", JSMN_ERROR_INVAL, 3)); + check(parse("{\"intVar\" : 01}", JSMN_ERROR_INVAL, 3)); + check(parse("{\"floatVar\" : .345}", JSMN_ERROR_INVAL, 3)); + check(parse("{\"floatVar\" : -}", JSMN_ERROR_INVAL, 3)); + check(parse("{\"floatVar\" : 12.}", JSMN_ERROR_INVAL, 3)); + check(parse("{\"floatVar\" : 12.}", JSMN_ERROR_INVAL, 3)); + check(parse("{\"floatVar\" : +12}", JSMN_ERROR_INVAL, 3)); + check(parse("{\"floatVar\" : 12.345e-}", JSMN_ERROR_INVAL, 3)); +#else + check(parse("{\"boolVar\" : tru }", 3, 3, JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "boolVar", 1, JSMN_PRIMITIVE, "tru")); + check(parse("{\"boolVar\" : falsee }", 3, 3, JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "boolVar", 1, JSMN_PRIMITIVE, "falsee")); + check(parse("{\"nullVar\" : nulm }", 3, 3, JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "nullVar", 1, JSMN_PRIMITIVE, "nulm")); + check(parse("{\"intVar\" : 01}", 3, 3, JSMN_OBJECT, -1, -1, 1, JSMN_STRING, + "intVar", 1, JSMN_PRIMITIVE, "01")); + check(parse("{\"floatVar\" : .345}", 3, 3, JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "floatVar", 1, JSMN_PRIMITIVE, ".345")); + check(parse("{\"floatVar\" : -}", 3, 3, JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "floatVar", 1, JSMN_PRIMITIVE, "-")); + check(parse("{\"floatVar\" : 12.}", 3, 3, JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "floatVar", 1, JSMN_PRIMITIVE, "12.")); + check(parse("{\"floatVar\" : 12.}", 3, 3, JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "floatVar", 1, JSMN_PRIMITIVE, "12.")); + check(parse("{\"floatVar\" : +12}", 3, 3, JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "floatVar", 1, JSMN_PRIMITIVE, "+12")); + check(parse("{\"floatVar\" : 12.345e-}", 3, 3, JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "floatVar", 1, JSMN_PRIMITIVE, "12.345e-")); +#endif return 0; } @@ -89,10 +141,26 @@ int test_string(void) { check(parse("{\"a\":[\"\\u0280\"]}", 4, 4, JSMN_OBJECT, -1, -1, 1, JSMN_STRING, "a", 1, JSMN_ARRAY, -1, -1, 1, JSMN_STRING, "\\u0280", 0)); + /* \xc2\xa9 is the copyright symbol in UTF-8 */ + check(parse("{\"a\":\"str\xc2\xa9\"}", 3, 3, JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "a", 1, JSMN_STRING, "str\xc2\xa9", 0)); +#ifdef JSMN_STRICT + check(parse("{\"a\":\"str\nstr\"}", JSMN_ERROR_INVAL, 3)); 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)); + check(parse("{\"a\":[\"\\u028\"]}", JSMN_ERROR_INVAL, 4)); +#else + check(parse("{\"a\":\"str\nstr\"}", 3, 3, JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "a", 1, JSMN_STRING, "str\nstr", 0)); + check(parse("{\"a\":\"str\\uFFGFstr\"}", 3, 3, JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "a", 1, JSMN_STRING, "str\\uFFGFstr", 0)); + check(parse("{\"a\":\"str\\u@FfF\"}", 3, 3, JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "a", 1, JSMN_STRING, "str\\u@FfF", 0)); + check(parse("{\"a\":[\"\\u028\"]}", 4, 4, JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "a", 1, JSMN_ARRAY, -1, -1, 1, + JSMN_STRING, "\\u028", 0)); +#endif return 0; } @@ -119,7 +187,6 @@ int test_partial_string(void) { } int test_partial_array(void) { -#ifdef JSMN_STRICT int r; unsigned long i; jsmn_parser p; @@ -138,7 +205,6 @@ int test_partial_array(void) { check(r == JSMN_ERROR_PART); } } -#endif return 0; } @@ -177,12 +243,13 @@ int test_unquoted_keys(void) { const char *js; jsmn_init(&p); - js = "key1: \"value\"\nkey2 : 123"; + js = "{key1: \"value\", key2 : 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")); + check(tokeq(js, tok, 5, JSMN_OBJECT, -1, -1, 2, JSMN_PRIMITIVE, "key1", + JSMN_STRING, "value", 0, JSMN_PRIMITIVE, "key2", + JSMN_PRIMITIVE, "123")); #endif return 0; } @@ -210,13 +277,6 @@ 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; @@ -280,9 +340,64 @@ int test_count(void) { return 0; } -int test_nonstrict(void) { -#ifndef JSMN_STRICT +int test_unenclosed(void) { const char *js; + js = "1234"; + check(parse(js, 1, 1, JSMN_PRIMITIVE, "1234")); + + js = "false"; + check(parse(js, 1, 1, JSMN_PRIMITIVE, "false")); + + js = "0garbage"; +#ifdef JSMN_STRICT + check(parse(js, JSMN_ERROR_INVAL, 1)); +#else + check(parse(js, 1, 1, JSMN_PRIMITIVE, "0garbage")); +#endif + + js = "\"0garbage\""; + check(parse(js, 1, 1, JSMN_STRING, "0garbage", 0)); + + js = "\"0garbage"; + check(parse(js, JSMN_ERROR_PART, 1)); + + js = "1234\"0garbage\""; + check(parse(js, 2, 2, JSMN_PRIMITIVE, "1234", JSMN_STRING, "0garbage", 0)); + + js = "\"0garbage\"1234"; + check(parse(js, 2, 2, JSMN_STRING, "0garbage", 0, JSMN_PRIMITIVE, "1234")); + + js = " 1234"; + check(parse(js, 1, 1, JSMN_PRIMITIVE, "1234")); + + js = "1234 "; + check(parse(js, 1, 1, JSMN_PRIMITIVE, "1234")); + + js = " 1234 "; + check(parse(js, 1, 1, JSMN_PRIMITIVE, "1234")); + +#ifdef JSMN_STRICT + js = "fal"; + check(parse(js, JSMN_ERROR_PART, 1)); + + js = "fal "; + check(parse(js, JSMN_ERROR_INVAL, 1)); +#else + js = "fal"; + check(parse(js, 1, 1, JSMN_PRIMITIVE, "fal")); + + js = "fal "; + check(parse(js, 1, 1, JSMN_PRIMITIVE, "fal")); +#endif + + js = "\"a\": 0"; + check(parse(js, JSMN_ERROR_INVAL, 2)); + + js = "\"a\", 0"; + check(parse(js, JSMN_ERROR_INVAL, 2)); + + /* XXX No longer valid after RFC 8259 fixes +#ifndef JSMN_STRICT js = "a: 0garbage"; check(parse(js, 2, 2, JSMN_PRIMITIVE, "a", JSMN_PRIMITIVE, "0garbage")); @@ -290,12 +405,14 @@ int test_nonstrict(void) { 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. */ + /* XXX No longer valid after RFC 8259 fixes js = "\"key {1\": 1234"; check(parse(js, 2, 2, JSMN_STRING, "key {1", 1, JSMN_PRIMITIVE, "1234")); - #endif + */ return 0; } @@ -305,6 +422,7 @@ int test_unmatched_brackets(void) { 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"; @@ -336,6 +454,31 @@ int test_object_key(void) { return 0; } +int test_multiple_objects(void) { + const char *js; + js = "true {\"def\": 123}"; + check(parse(js, 4, 4, JSMN_PRIMITIVE, "true", JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "def", 1, JSMN_PRIMITIVE, "123")); + + js = "{\"def\": 123} true"; + check(parse(js, 4, 4, JSMN_OBJECT, -1, -1, 1, JSMN_STRING, "def", 1, + JSMN_PRIMITIVE, "123", JSMN_PRIMITIVE, "true")); + + js = "true{\"def\": 123}"; + check(parse(js, 4, 4, JSMN_PRIMITIVE, "true", JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "def", 1, JSMN_PRIMITIVE, "123")); + + js = "[true, \"abc\"]{\"def\": 123}"; + check(parse(js, 6, 6, JSMN_ARRAY, -1, -1, 2, JSMN_PRIMITIVE, "true", + JSMN_STRING, "abc", 0, JSMN_OBJECT, -1, -1, 1, + JSMN_STRING, "def", 1, JSMN_PRIMITIVE, "123")); + + /* Issue #27 */ + js = "{ \"name\" : \"Jack\", \"age\" : 27 } { \"name\" : \"Anna\", "; + check(parse(js, JSMN_ERROR_PART, 8)); + return 0; +} + int main(void) { test(test_empty, "test for a empty JSON objects/arrays"); test(test_object, "test for a JSON objects"); @@ -349,11 +492,11 @@ 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 #27"); test(test_count, "test tokens count estimation"); - test(test_nonstrict, "test for non-strict mode"); + test(test_unenclosed, "test for non-strict mode"); test(test_unmatched_brackets, "test for unmatched brackets"); test(test_object_key, "test for key type"); + test(test_multiple_objects, "test parsing multiple items at once"); printf("\nPASSED: %d\nFAILED: %d\n", test_passed, test_failed); return (test_failed > 0); }