diff --git a/READMEs/README.json-lejp.md b/READMEs/README.json-lejp.md index 1471c17d2..45a4a1e40 100644 --- a/READMEs/README.json-lejp.md +++ b/READMEs/README.json-lejp.md @@ -24,6 +24,21 @@ The features are: - collates utf-8 text payloads into a 250-byte chunk buffer in the json parser context object for ease of access +## LEJP Context initialization + +lejp doesn't allocate at all, you define a `struct lejp_ctx` usually on the +stack somewhere, and call `lejp_construct()` to initialize it. + +To minimize surprises as lejp evolves, there is now a `flags` member of the +ctx, which defaults to zero for compatibility with older versions. After +the `lejp_construct()` call, you can set `ctx.flags` to indicate you want +newer options + +|lejp flags|Meaning| +|---|---| +|LEJP_FLAG_FEAT_OBJECT_INDEXES|Provide indexes for { x, y, x } lists same as for arrays| +|LEJP_FLAG_LATEST|Alias indicating you want the "best" current options, even if incompatible with old behaviours| + ## Type handling LEJP leaves all numbers in text form, they are signalled in different callbacks @@ -87,6 +102,20 @@ or the match index from your path array starting from 1 for the first entry. +## Details of object and array indexes + +LEJP maintains a "stack" of index counters, each element represents one level +in the current hierarchy that may have a list or array of objects in it. +The amount of levels currently is held in `ctx->ipos`, and `ctx->i[]` holds +`uint16_t` index counts for each level. + +By querying these, you can understand at which element index in a hierarchy of +arrays in the JSON you are at, unambiguously. + +By default that is done for each `[]` array level, if you set `ctx.flags` with +`LEJP_FLAG_FEAT_OBJECT_INDEXES` option, it is also done for each `{}` object +level, which can also take comma-separated lists that need index tracking. + ## Comparison with LECP (CBOR parser) LECP is based on the same principles as LEJP and shares most of the callbacks. diff --git a/include/libwebsockets/lws-lejp.h b/include/libwebsockets/lws-lejp.h index c44431edf..89d4c4c22 100644 --- a/include/libwebsockets/lws-lejp.h +++ b/include/libwebsockets/lws-lejp.h @@ -245,6 +245,10 @@ struct lejp_ctx { /* short */ uint16_t uni; +#define LEJP_FLAG_FEAT_OBJECT_INDEXES (1 << 0) +#define LEJP_FLAG_LATEST \ + (LEJP_FLAG_FEAT_OBJECT_INDEXES) + uint16_t flags; /* char */ diff --git a/lib/misc/lejp.c b/lib/misc/lejp.c index 6caa882b7..a3ba232d2 100644 --- a/lib/misc/lejp.c +++ b/lib/misc/lejp.c @@ -82,6 +82,7 @@ lejp_construct(struct lejp_ctx *ctx, ctx->path[0] = '\0'; ctx->user = user; ctx->line = 1; + ctx->flags = 0; /* user may set after construction */ ctx->pst_sp = 0; ctx->pst[0].callback = callback; @@ -287,6 +288,17 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len) ret = LEJP_REJECT_IDLE_NO_BRACE; goto reject; } + + if (ctx->flags & LEJP_FLAG_FEAT_OBJECT_INDEXES) { + /* since insides of {} can have ',', we should + * add an index level so we can count them + */ + ctx->i[ctx->ipos++] = 0; + if (ctx->ipos > LWS_ARRAY_SIZE(ctx->i)) { + ret = LEJP_REJECT_MP_DELIM_ISTACK; + goto reject; + } + } if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_OBJECT_START)) goto reject_callback; @@ -458,6 +470,16 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len) ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END; c = LEJP_MEMBERS; lejp_check_path_match(ctx); + if (ctx->flags & LEJP_FLAG_FEAT_OBJECT_INDEXES) { + /* since insides of {} can have ',', we should + * add an index level so we can count them + */ + ctx->i[ctx->ipos++] = 0; + if (ctx->ipos > LWS_ARRAY_SIZE(ctx->i)) { + ret = LEJP_REJECT_MP_DELIM_ISTACK; + goto reject; + } + } if (ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_OBJECT_START)) goto reject_callback; @@ -499,7 +521,9 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len) ctx->pst[ctx->pst_sp].ppos = (unsigned char) ctx->st[ctx->sp - 1].p; ctx->ipos = (unsigned char)ctx->st[ctx->sp - 1].i; - } + } else + if (ctx->flags & LEJP_FLAG_FEAT_OBJECT_INDEXES) + ctx->ipos--; ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0'; if (ctx->path_match && ctx->pst[ctx->pst_sp].ppos <= ctx->path_match_len) @@ -643,6 +667,11 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len) if (c == ',') { /* increment this stack level's index */ ctx->st[ctx->sp].s = LEJP_M_P; + + if (ctx->flags & LEJP_FLAG_FEAT_OBJECT_INDEXES) + if (ctx->ipos) + ctx->i[ctx->ipos - 1]++; + if (!ctx->sp) { ctx->pst[ctx->pst_sp].ppos = 0; /* @@ -662,11 +691,15 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len) */ ctx->path_match = 0; + lejp_check_path_match(ctx); + if (ctx->st[ctx->sp - 1].s != LEJP_MP_ARRAY_END) break; /* top level is definitely an array... */ - if (ctx->ipos) - ctx->i[ctx->ipos - 1]++; + if (!(ctx->flags & LEJP_FLAG_FEAT_OBJECT_INDEXES)) + if (ctx->ipos) + ctx->i[ctx->ipos - 1]++; + ctx->st[ctx->sp].s = LEJP_MP_VALUE; break; } @@ -687,7 +720,10 @@ lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len) ctx->pst[ctx->pst_sp].ppos = (unsigned char) ctx->st[ctx->sp - 1].p; ctx->ipos = (unsigned char)ctx->st[ctx->sp - 1].i; - } + } else + if (ctx->flags & LEJP_FLAG_FEAT_OBJECT_INDEXES) + ctx->ipos--; + ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0'; if (ctx->path_match && ctx->pst[ctx->pst_sp].ppos <= ctx->path_match_len) @@ -731,8 +767,11 @@ pop_level: if (ctx->sp) { ctx->pst[ctx->pst_sp].ppos = (unsigned char)ctx->st[ctx->sp].p; ctx->ipos = (unsigned char)ctx->st[ctx->sp].i; - } + } else + if (ctx->flags & LEJP_FLAG_FEAT_OBJECT_INDEXES) + ctx->ipos--; ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0'; + if (ctx->path_match && ctx->pst[ctx->pst_sp].ppos <= ctx->path_match_len) /* @@ -761,13 +800,14 @@ array_end: ctx->pst[ctx->pst_sp].ppos = (unsigned char) ctx->st[ctx->sp - 1].p; ctx->path[ctx->pst[ctx->pst_sp].ppos] = '\0'; + lejp_check_path_match(ctx); break; } if (c != ']') { ret = LEJP_REJECT_MP_ARRAY_END_MISSING; goto reject; } - + lejp_check_path_match(ctx); ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END; ctx->pst[ctx->pst_sp].callback(ctx, LEJPCB_ARRAY_END); if (defer) { diff --git a/minimal-examples-lowlevel/api-tests/api-test-lejp/main.c b/minimal-examples-lowlevel/api-tests/api-test-lejp/main.c index 895c7da95..c4eeec1a4 100644 --- a/minimal-examples-lowlevel/api-tests/api-test-lejp/main.c +++ b/minimal-examples-lowlevel/api-tests/api-test-lejp/main.c @@ -132,6 +132,11 @@ static const char * const json_tests[] = { "{" /* test 11: array of arrays */ "\"array1\": [[\"a\", \"b\", \"b1\"], [\"c\", \"d\", \"d1\"]]," "\"array2\": [[\"e\", \"f\", \"f1\"], [\"g\", \"h\", \"h1\"]]" + "}", + + "{" /* test 12: test 11 but done with LEJP_FLAG_FEAT_OBJECT_INDEXES */ + "\"array1\": [[\"a\", \"b\", \"b1\"], [\"c\", \"d\", \"d1\"]]," + "\"array2\": [[\"e\", \"f\", \"f1\"], [\"g\", \"h\", \"h1\"]]" "}" }; @@ -350,69 +355,118 @@ struct lejp_results { { 2, 0, 0, { 0 }, "", "123" }, { 16, 0, 0, { 0 }, "", "123" }, { 5, 0, 0, { 0 }, "array1", "123" }, - { 14, 0, 0, { 0 }, "array1[]", "123" }, - { 14, 1, 0, { 0, }, "array1[][]", "123" }, - { 11, 2, 0, { 0, 0, }, "array1[][]", "" }, - { 77, 2, 0, { 0, 0, }, "array1[][]", "a" }, - { 11, 2, 0, { 0, 1, }, "array1[][]", "" }, - { 77, 2, 0, { 0, 1, }, "array1[][]", "b" }, - { 11, 2, 0, { 0, 2, }, "array1[][]", "" }, - { 77, 2, 0, { 0, 2, }, "array1[][]", "b1" }, - { 15, 1, 0, { 0, }, "array1[]", "b1" }, - { 14, 1, 0, { 1, }, "array1[][]", "b1" }, - { 11, 2, 0, { 1, 0, }, "array1[][]", "" }, - { 77, 2, 0, { 1, 0, }, "array1[][]", "c" }, - { 11, 2, 0, { 1, 1, }, "array1[][]", "" }, - { 77, 2, 0, { 1, 1, }, "array1[][]", "d" }, - { 11, 2, 0, { 1, 2, }, "array1[][]", "" }, - { 77, 2, 0, { 1, 2, }, "array1[][]", "d1" }, - { 15, 1, 0, { 1, }, "array1[]", "d1" }, - { 15, 1, 0, { 1, }, "array1[]", "d1" }, + { 14, 0, 2, { 0 }, "array1[]", "123" }, + { 14, 1, 1, { 0, }, "array1[][]", "123" }, + { 11, 2, 1, { 0, 0, }, "array1[][]", "" }, + { 77, 2, 1, { 0, 0, }, "array1[][]", "a" }, + { 11, 2, 1, { 0, 1, }, "array1[][]", "" }, + { 77, 2, 1, { 0, 1, }, "array1[][]", "b" }, + { 11, 2, 1, { 0, 2, }, "array1[][]", "" }, + { 77, 2, 1, { 0, 2, }, "array1[][]", "b1" }, + { 15, 1, 2, { 0, }, "array1[]", "b1" }, + { 14, 1, 1, { 1, }, "array1[][]", "b1" }, + { 11, 2, 1, { 1, 0, }, "array1[][]", "" }, + { 77, 2, 1, { 1, 0, }, "array1[][]", "c" }, + { 11, 2, 1, { 1, 1, }, "array1[][]", "" }, + { 77, 2, 1, { 1, 1, }, "array1[][]", "d" }, + { 11, 2, 1, { 1, 2, }, "array1[][]", "" }, + { 77, 2, 1, { 1, 2, }, "array1[][]", "d1" }, + { 15, 1, 2, { 1, }, "array1[]", "d1" }, + { 15, 1, 2, { 1, }, "array1[]", "d1" }, { 5, 1, 0, { 1, }, "array2", "d1" }, - { 14, 1, 0, { 1, }, "array2[]", "d1" }, - { 14, 2, 0, { 1, 0, }, "array2[][]", "d1" }, - { 11, 3, 0, { 1, 0, 0, }, "array2[][]", "" }, - { 77, 3, 0, { 1, 0, 0, }, "array2[][]", "e" }, - { 11, 3, 0, { 1, 0, 1, }, "array2[][]", "" }, - { 77, 3, 0, { 1, 0, 1, }, "array2[][]", "f" }, - { 11, 3, 0, { 1, 0, 2, }, "array2[][]", "" }, - { 77, 3, 0, { 1, 0, 2, }, "array2[][]", "f1" }, - { 15, 2, 0, { 1, 0, }, "array2[]", "f1" }, - { 14, 2, 0, { 1, 1, }, "array2[][]", "f1" }, - { 11, 3, 0, { 1, 1, 0, }, "array2[][]", "" }, - { 77, 3, 0, { 1, 1, 0, }, "array2[][]", "g" }, - { 11, 3, 0, { 1, 1, 1, }, "array2[][]", "" }, - { 77, 3, 0, { 1, 1, 1, }, "array2[][]", "h" }, - { 11, 3, 0, { 1, 1, 2, }, "array2[][]", "" }, - { 77, 3, 0, { 1, 1, 2, }, "array2[][]", "h1" }, - { 15, 2, 0, { 1, 1, }, "array2[]", "h1" }, - { 15, 2, 0, { 1, 1, }, "array2[]", "h1" }, + { 14, 1, 2, { 1, }, "array2[]", "d1" }, + { 14, 2, 1, { 1, 0, }, "array2[][]", "d1" }, + { 11, 3, 1, { 1, 0, 0, }, "array2[][]", "" }, + { 77, 3, 1, { 1, 0, 0, }, "array2[][]", "e" }, + { 11, 3, 1, { 1, 0, 1, }, "array2[][]", "" }, + { 77, 3, 1, { 1, 0, 1, }, "array2[][]", "f" }, + { 11, 3, 1, { 1, 0, 2, }, "array2[][]", "" }, + { 77, 3, 1, { 1, 0, 2, }, "array2[][]", "f1" }, + { 15, 2, 2, { 1, 0, }, "array2[]", "f1" }, + { 14, 2, 1, { 1, 1, }, "array2[][]", "f1" }, + { 11, 3, 1, { 1, 1, 0, }, "array2[][]", "" }, + { 77, 3, 1, { 1, 1, 0, }, "array2[][]", "g" }, + { 11, 3, 1, { 1, 1, 1, }, "array2[][]", "" }, + { 77, 3, 1, { 1, 1, 1, }, "array2[][]", "h" }, + { 11, 3, 1, { 1, 1, 2, }, "array2[][]", "" }, + { 77, 3, 1, { 1, 1, 2, }, "array2[][]", "h1" }, + { 15, 2, 2, { 1, 1, }, "array2[]", "h1" }, + { 15, 2, 2, { 1, 1, }, "array2[]", "h1" }, { 17, 2, 0, { 1, 1, }, "array2[]", "h1" }, { 3, 2, 0, { 1, 1, }, "array2[]", "h1" }, +}, r12[] = { /* test 11 but done with LEJP_FLAG_FEAT_OBJECT_INDEXES */ + { 0, 0, 0, { 0 }, "", "h1" }, + { 2, 0, 0, { 0 }, "", "h1" }, + { 16, 1, 0, { 0, }, "", "h1" }, + { 5, 1, 0, { 0, }, "array1", "h1" }, + { 14, 1, 2, { 0, }, "array1[]", "h1" }, + { 14, 2, 1, { 0, 0, }, "array1[][]", "h1" }, + { 11, 3, 1, { 0, 0, 0, }, "array1[][]", "" }, + { 77, 3, 1, { 0, 0, 0, }, "array1[][]", "a" }, + { 11, 3, 1, { 0, 0, 1, }, "array1[][]", "" }, + { 77, 3, 1, { 0, 0, 1, }, "array1[][]", "b" }, + { 11, 3, 1, { 0, 0, 2, }, "array1[][]", "" }, + { 77, 3, 1, { 0, 0, 2, }, "array1[][]", "b1" }, + { 15, 2, 2, { 0, 0, }, "array1[]", "b1" }, + { 14, 2, 1, { 0, 1, }, "array1[][]", "b1" }, + { 11, 3, 1, { 0, 1, 0, }, "array1[][]", "" }, + { 77, 3, 1, { 0, 1, 0, }, "array1[][]", "c" }, + { 11, 3, 1, { 0, 1, 1, }, "array1[][]", "" }, + { 77, 3, 1, { 0, 1, 1, }, "array1[][]", "d" }, + { 11, 3, 1, { 0, 1, 2, }, "array1[][]", "" }, + { 77, 3, 1, { 0, 1, 2, }, "array1[][]", "d1" }, + { 15, 2, 2, { 0, 1, }, "array1[]", "d1" }, + { 15, 1, 2, { 0, 1, }, "array1[]", "d1" }, + { 5, 1, 0, { 1, }, "array2", "d1" }, + { 14, 1, 2, { 1, }, "array2[]", "d1" }, + { 14, 2, 1, { 1, 0, }, "array2[][]", "d1" }, + { 11, 3, 1, { 1, 0, 0, }, "array2[][]", "" }, + { 77, 3, 1, { 1, 0, 0, }, "array2[][]", "e" }, + { 11, 3, 1, { 1, 0, 1, }, "array2[][]", "" }, + { 77, 3, 1, { 1, 0, 1, }, "array2[][]", "f" }, + { 11, 3, 1, { 1, 0, 2, }, "array2[][]", "" }, + { 77, 3, 1, { 1, 0, 2, }, "array2[][]", "f1" }, + { 15, 2, 2, { 1, 0, }, "array2[]", "f1" }, + { 14, 2, 1, { 1, 1, }, "array2[][]", "f1" }, + { 11, 3, 1, { 1, 1, 0, }, "array2[][]", "" }, + { 77, 3, 1, { 1, 1, 0, }, "array2[][]", "g" }, + { 11, 3, 1, { 1, 1, 1, }, "array2[][]", "" }, + { 77, 3, 1, { 1, 1, 1, }, "array2[][]", "h" }, + { 11, 3, 1, { 1, 1, 2, }, "array2[][]", "" }, + { 77, 3, 1, { 1, 1, 2, }, "array2[][]", "h1" }, + { 15, 2, 2, { 1, 1, }, "array2[]", "h1" }, + { 15, 1, 2, { 1, }, "array2[]", "h1" }, + { 17, 1, 0, { 1, }, "array2[]", "h1" }, + { 3, 1, 0, { 1, }, "array2[]", "h1" }, }; - +static const char * const tok[] = { + "something", +}, * const tok_test11[] = { /* matches for test 11, 12 */ + "*[][]", + "*[]", +}; struct lejp_results_pkg { const struct lejp_results *r; size_t len; + const char * const *tokens; + size_t tokens_len; + uint16_t ctx_flags; } rpkg[] = { - { r1, LWS_ARRAY_SIZE(r1) }, - { r2, LWS_ARRAY_SIZE(r2) }, - { r3, LWS_ARRAY_SIZE(r3) }, - { r4, LWS_ARRAY_SIZE(r4) }, - { r5, LWS_ARRAY_SIZE(r5) }, - { r6, LWS_ARRAY_SIZE(r6) }, - { r7, LWS_ARRAY_SIZE(r7) }, - { r8, LWS_ARRAY_SIZE(r8) }, - { r9, LWS_ARRAY_SIZE(r9) }, - { r10, LWS_ARRAY_SIZE(r10) }, - { r11, LWS_ARRAY_SIZE(r11) }, -}; - - -static const char * const tok[] = { - "something", + { r1, LWS_ARRAY_SIZE(r1), tok, LWS_ARRAY_SIZE(tok), 0 }, + { r2, LWS_ARRAY_SIZE(r2), tok, LWS_ARRAY_SIZE(tok), 0 }, + { r3, LWS_ARRAY_SIZE(r3), tok, LWS_ARRAY_SIZE(tok), 0 }, + { r4, LWS_ARRAY_SIZE(r4), tok, LWS_ARRAY_SIZE(tok), 0 }, + { r5, LWS_ARRAY_SIZE(r5), tok, LWS_ARRAY_SIZE(tok), 0 }, + { r6, LWS_ARRAY_SIZE(r6), tok, LWS_ARRAY_SIZE(tok), 0 }, + { r7, LWS_ARRAY_SIZE(r7), tok, LWS_ARRAY_SIZE(tok), 0 }, + { r8, LWS_ARRAY_SIZE(r8), tok, LWS_ARRAY_SIZE(tok), 0 }, + { r9, LWS_ARRAY_SIZE(r9), tok, LWS_ARRAY_SIZE(tok), 0 }, + { r10, LWS_ARRAY_SIZE(r10), tok, LWS_ARRAY_SIZE(tok), 0 }, + { r11, LWS_ARRAY_SIZE(r11), tok_test11, LWS_ARRAY_SIZE(tok_test11), 0 }, + { r12, LWS_ARRAY_SIZE(r12), tok_test11, LWS_ARRAY_SIZE(tok_test11), + LEJP_FLAG_FEAT_OBJECT_INDEXES }, }; @@ -491,7 +545,8 @@ int main(int argc, const char **argv) lwsl_user("%s: ++++++++++++++++ test %d\n", __func__, m + 1); step = 0; - lejp_construct(&ctx, test_cb, NULL, tok, LWS_ARRAY_SIZE(tok)); + lejp_construct(&ctx, test_cb, NULL, rpkg[m].tokens, (uint8_t)rpkg[m].tokens_len); + ctx.flags = rpkg[m].ctx_flags; lwsl_hexdump_info(json_tests[m], strlen(json_tests[m]));