diff --git a/Makefile b/Makefile index dcbdd89d..4b9a2c8f 100644 --- a/Makefile +++ b/Makefile @@ -21,6 +21,9 @@ simple_example: example/simple.c jsmn.h jsondump: example/jsondump.c jsmn.h $(CC) $(LDFLAGS) $< -o $@ +json_sequence: example/json_sequence.o jsmn.h + $(CC) $(LDFLAGS) $< -o $@ + fmt: clang-format -i jsmn.h test/*.[ch] example/*.[ch] @@ -31,6 +34,7 @@ clean: rm -f *.o example/*.o rm -f simple_example rm -f jsondump + rm -f json_sequence .PHONY: clean test diff --git a/example/json_sequence.c b/example/json_sequence.c new file mode 100644 index 00000000..1ab386e5 --- /dev/null +++ b/example/json_sequence.c @@ -0,0 +1,34 @@ +#include +#include +#include +#include "../jsmn.h" + +static const char *JSON_STRING = + "{\"@timestamp\":\"2018-11-25T18:45:00\", \"programname\":\"my_prog\", \"procid\":\"123\", \"severity\":\"info\", \"message\":\"Started\"}\n" + "{\"@timestamp\":\"2018-11-25T18:45:01\", \"programname\":\"my_prog\", \"procid\":\"123\", \"severity\":\"warn\", \"message\":\"File is too large\"}\n" + "{\"@timestamp\":\"2018-11-25T18:45:03\", \"programname\":\"oom_killer\", \"procid\":\"42\", \"severity\":\"info\", \"message\":\"Process 123 (my_prog) was killed\"}"; + +int main() { + int r; + jsmn_parser p; + jsmntok_t t[11]; /* We expect no more than 11 tokens in one JSON object */ + + jsmn_init(&p); + size_t len = strlen(JSON_STRING); + + r = jsmn_parse_next(&p, JSON_STRING, len, t, 11); + while (r > 0) { + printf("%.*s %.*s[%.*s]: <%.*s> %.*s\n", + t[2].end - t[2].start, JSON_STRING + t[2].start, + t[4].end - t[4].start, JSON_STRING + t[4].start, + t[6].end - t[6].start, JSON_STRING + t[6].start, + t[8].end - t[8].start, JSON_STRING + t[8].start, + t[10].end - t[10].start, JSON_STRING + t[10].start); + r = jsmn_parse_next(&p, JSON_STRING, len, t, 11); + } + if (r < 0) { + printf("Failed to parse JSON: %d\n", r); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} diff --git a/jsmn.h b/jsmn.h index 251359cc..0f6ebd94 100644 --- a/jsmn.h +++ b/jsmn.h @@ -99,6 +99,13 @@ JSMN_API void jsmn_init(jsmn_parser *parser); JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, jsmntok_t *tokens, unsigned int num_tokens); +/** + * Run JSON parser. Unlike jsmn_parse it stops after a next complete JSON + * object is parsed. + */ +JSMN_API int jsmn_parse_next(jsmn_parser *parser, const char *js, size_t len, + jsmntok_t *tokens, unsigned int num_tokens); + #ifndef JSMN_HEADER /** * Allocates a fresh unused token from the token pool. @@ -260,8 +267,9 @@ static int jsmn_parse_string(jsmn_parser *parser, const char *js, size_t len, /** * Parse JSON string and fill tokens. */ -JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, - jsmntok_t *tokens, unsigned int num_tokens) { +static int jsmn_parse_full(jsmn_parser *parser, const char *js, size_t len, + jsmntok_t *tokens, unsigned int num_tokens, + int parse_next) { int r; int i; jsmntok_t *token; @@ -342,19 +350,27 @@ JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, } } #endif + if (parse_next && parser->toksuper == -1) + len = parser->pos; 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++; + if (parser->toksuper != -1) { + if (tokens != NULL) + tokens[parser->toksuper].size++; + } else if (parse_next) { + len = parser->pos; + } break; case '\t': case '\r': case '\n': case ' ': + if (parse_next && parser->toksuper == -1 && count > 0) + len = parser->pos; break; case ':': parser->toksuper = parser->toknext - 1; @@ -409,8 +425,12 @@ JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, if (r < 0) return r; count++; - if (parser->toksuper != -1 && tokens != NULL) - tokens[parser->toksuper].size++; + if (parser->toksuper != -1) { + if (tokens != NULL) + tokens[parser->toksuper].size++; + } else if (parse_next) { + len = parser->pos; + } break; #ifdef JSMN_STRICT @@ -433,6 +453,20 @@ JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, return count; } +JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, + jsmntok_t *tokens, unsigned int num_tokens) { + return jsmn_parse_full(parser, js, len, tokens, num_tokens, 0); +} + +JSMN_API int jsmn_parse_next(jsmn_parser *parser, const char *js, size_t len, + jsmntok_t *tokens, unsigned int num_tokens) { + int r; + r = jsmn_parse_full(parser, js, len, tokens, num_tokens, 1); + if (r >= 0) + parser->toknext = 0; + return r; +} + /** * Creates a new parser based over a given buffer with an array of tokens * available. diff --git a/test/tests.c b/test/tests.c index c473a233..8704438e 100644 --- a/test/tests.c +++ b/test/tests.c @@ -317,6 +317,68 @@ int test_unmatched_brackets(void) { return 0; } +int test_json_sequence(void) { + const char *js; + js = " null\t\"2\"[\r][ 3 ]\r\n[ {\n}] [ 4, { } ]"; + check(parse(js, 10, 10, + JSMN_PRIMITIVE, "null", + JSMN_STRING, "2", 0, + JSMN_ARRAY, 9, 12, 0, + JSMN_ARRAY, 12, 17, 1, + JSMN_PRIMITIVE, "3", + JSMN_ARRAY, 19, 25, 1, + JSMN_OBJECT, 21, 24, 0, + JSMN_ARRAY, 26, 36, 2, + JSMN_PRIMITIVE, "4", + JSMN_OBJECT, 31, 34, 0)); + return 0; +} + +int test_json_sequence_by_one(void) { + const char *js; + size_t js_len; + int r; + jsmn_parser p; + jsmntok_t tokens[3]; + + js = " null\t\"2\"[\r][ 3 ]\r\n[ {\n}] [ 4, { } ]"; + js_len = strlen(js); + + jsmn_init(&p); + + r = jsmn_parse_next(&p, js, js_len, tokens, 3); + check(r == 1); + check(tokeq(js, tokens, r, + JSMN_PRIMITIVE, "null")); + r = jsmn_parse_next(&p, js, js_len, tokens, 3); + check(r == 1); + check(tokeq(js, tokens, r, + JSMN_STRING, "2", 0)); + r = jsmn_parse_next(&p, js, js_len, tokens, 3); + check(r == 1); + check(tokeq(js, tokens, r, + JSMN_ARRAY, 9, 12, 0)); + r = jsmn_parse_next(&p, js, js_len, tokens, 3); + check(r == 2); + check(tokeq(js, tokens, r, + JSMN_ARRAY, 12, 17, 1, + JSMN_PRIMITIVE, "3")); + r = jsmn_parse_next(&p, js, js_len, tokens, 3); + check(r == 2); + check(tokeq(js, tokens, r, + JSMN_ARRAY, 19, 25, 1, + JSMN_OBJECT, 21, 24, 0)); + r = jsmn_parse_next(&p, js, js_len, tokens, 3); + check(r == 3); + check(tokeq(js, tokens, r, + JSMN_ARRAY, 26, 36, 2, + JSMN_PRIMITIVE, "4", + JSMN_OBJECT, 31, 34, 0)); + r = jsmn_parse_next(&p, js, js_len, tokens, 3); + check(r == 0); + return 0; +} + int main(void) { test(test_empty, "test for a empty JSON objects/arrays"); test(test_object, "test for a JSON objects"); @@ -334,6 +396,8 @@ int main(void) { 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_json_sequence, "test for many JSONs in one string"); + test(test_json_sequence_by_one, "test for many JSONs in one string by one"); printf("\nPASSED: %d\nFAILED: %d\n", test_passed, test_failed); return (test_failed > 0); }