diff --git a/mongoose.c b/mongoose.c index 31e0cc4e..667ab917 100644 --- a/mongoose.c +++ b/mongoose.c @@ -2954,6 +2954,55 @@ static double mg_atod(const char *p, int len, int *numlen) { return d; } +// Iterate over object or array elements +size_t mg_json_next(struct mg_str obj, size_t ofs, struct mg_str *key, + struct mg_str *val) { + if (ofs >= obj.len) { + ofs = 0; // Out of boundaries, stop scanning + } else if (obj.len < 2 || (*obj.ptr != '{' && *obj.ptr != '[')) { + ofs = 0; // Not an array or object, stop + } else { + struct mg_str sub = mg_str_n(obj.ptr + ofs, obj.len - ofs); + if (ofs == 0) ofs++, sub.ptr++, sub.len--; + if (*obj.ptr == '[') { // Iterate over an array + int n = 0, o = mg_json_get(sub, "$", &n); + if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) { + ofs = 0; // Error parsing key, stop scanning + } else { + if (key) *key = mg_str_n(NULL, 0); + if (val) *val = mg_str_n(sub.ptr + o, (size_t) n); + ofs = (size_t) (&sub.ptr[o + n] - obj.ptr); + } + } else { // Iterate over an object + int n = 0, o = mg_json_get(sub, "$", &n); + if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) { + ofs = 0; // Error parsing key, stop scanning + } else { + if (key) *key = mg_str_n(sub.ptr + o, (size_t) n); + sub.ptr += o + n, sub.len -= (size_t) (o + n); + while (sub.len > 0 && *sub.ptr != ':') sub.len--, sub.ptr++; + if (sub.len > 0 && *sub.ptr == ':') sub.len--, sub.ptr++; + n = 0, o = mg_json_get(sub, "$", &n); + if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) { + ofs = 0; // Error parsing value, stop scanning + } else { + if (val) *val = mg_str_n(sub.ptr + o, (size_t) n); + ofs = (size_t) (&sub.ptr[o + n] - obj.ptr); + } + } + } + //MG_INFO(("SUB ofs %u %.*s", ofs, sub.len, sub.ptr)); + while (ofs && ofs < obj.len && + (obj.ptr[ofs] == ' ' || obj.ptr[ofs] == '\t' || + obj.ptr[ofs] == '\n' || obj.ptr[ofs] == '\r')) { + ofs++; + } + if (ofs && ofs < obj.len && obj.ptr[ofs] == ',') ofs++; + if (ofs > obj.len) ofs = 0; + } + return ofs; +} + int mg_json_get(struct mg_str json, const char *path, int *toklen) { const char *s = json.ptr; int len = (int) json.len; @@ -3049,8 +3098,8 @@ int mg_json_get(struct mg_str json, const char *path, int *toklen) { // printf("K %s [%.*s] [%.*s] %d %d %d %d %d\n", path, pos, path, n, // &s[i + 1], n, depth, ed, ci, ei); // NOTE(cpq): in the check sequence below is important. - // strncmp() must go first: it fails fast if the remaining length of - // the path is smaller than `n`. + // strncmp() must go first: it fails fast if the remaining length + // of the path is smaller than `n`. if (depth == ed && path[pos - 1] == '.' && strncmp(&s[i + 1], &path[pos], (size_t) n) == 0 && (path[pos + n] == '\0' || path[pos + n] == '.' || diff --git a/mongoose.h b/mongoose.h index 45f3dd91..f7852f03 100644 --- a/mongoose.h +++ b/mongoose.h @@ -1629,6 +1629,8 @@ char *mg_json_get_hex(struct mg_str json, const char *path, int *len); char *mg_json_get_b64(struct mg_str json, const char *path, int *len); bool mg_json_unescape(struct mg_str str, char *buf, size_t len); +size_t mg_json_next(struct mg_str obj, size_t ofs, struct mg_str *key, + struct mg_str *val); diff --git a/src/json.c b/src/json.c index 78dcf68c..985128a3 100644 --- a/src/json.c +++ b/src/json.c @@ -74,6 +74,55 @@ static double mg_atod(const char *p, int len, int *numlen) { return d; } +// Iterate over object or array elements +size_t mg_json_next(struct mg_str obj, size_t ofs, struct mg_str *key, + struct mg_str *val) { + if (ofs >= obj.len) { + ofs = 0; // Out of boundaries, stop scanning + } else if (obj.len < 2 || (*obj.ptr != '{' && *obj.ptr != '[')) { + ofs = 0; // Not an array or object, stop + } else { + struct mg_str sub = mg_str_n(obj.ptr + ofs, obj.len - ofs); + if (ofs == 0) ofs++, sub.ptr++, sub.len--; + if (*obj.ptr == '[') { // Iterate over an array + int n = 0, o = mg_json_get(sub, "$", &n); + if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) { + ofs = 0; // Error parsing key, stop scanning + } else { + if (key) *key = mg_str_n(NULL, 0); + if (val) *val = mg_str_n(sub.ptr + o, (size_t) n); + ofs = (size_t) (&sub.ptr[o + n] - obj.ptr); + } + } else { // Iterate over an object + int n = 0, o = mg_json_get(sub, "$", &n); + if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) { + ofs = 0; // Error parsing key, stop scanning + } else { + if (key) *key = mg_str_n(sub.ptr + o, (size_t) n); + sub.ptr += o + n, sub.len -= (size_t) (o + n); + while (sub.len > 0 && *sub.ptr != ':') sub.len--, sub.ptr++; + if (sub.len > 0 && *sub.ptr == ':') sub.len--, sub.ptr++; + n = 0, o = mg_json_get(sub, "$", &n); + if (n < 0 || o < 0 || (size_t) (o + n) > sub.len) { + ofs = 0; // Error parsing value, stop scanning + } else { + if (val) *val = mg_str_n(sub.ptr + o, (size_t) n); + ofs = (size_t) (&sub.ptr[o + n] - obj.ptr); + } + } + } + //MG_INFO(("SUB ofs %u %.*s", ofs, sub.len, sub.ptr)); + while (ofs && ofs < obj.len && + (obj.ptr[ofs] == ' ' || obj.ptr[ofs] == '\t' || + obj.ptr[ofs] == '\n' || obj.ptr[ofs] == '\r')) { + ofs++; + } + if (ofs && ofs < obj.len && obj.ptr[ofs] == ',') ofs++; + if (ofs > obj.len) ofs = 0; + } + return ofs; +} + int mg_json_get(struct mg_str json, const char *path, int *toklen) { const char *s = json.ptr; int len = (int) json.len; @@ -169,8 +218,8 @@ int mg_json_get(struct mg_str json, const char *path, int *toklen) { // printf("K %s [%.*s] [%.*s] %d %d %d %d %d\n", path, pos, path, n, // &s[i + 1], n, depth, ed, ci, ei); // NOTE(cpq): in the check sequence below is important. - // strncmp() must go first: it fails fast if the remaining length of - // the path is smaller than `n`. + // strncmp() must go first: it fails fast if the remaining length + // of the path is smaller than `n`. if (depth == ed && path[pos - 1] == '.' && strncmp(&s[i + 1], &path[pos], (size_t) n) == 0 && (path[pos + n] == '\0' || path[pos + n] == '.' || diff --git a/src/json.h b/src/json.h index 498aaa35..2cda8d29 100644 --- a/src/json.h +++ b/src/json.h @@ -19,3 +19,5 @@ char *mg_json_get_hex(struct mg_str json, const char *path, int *len); char *mg_json_get_b64(struct mg_str json, const char *path, int *len); bool mg_json_unescape(struct mg_str str, char *buf, size_t len); +size_t mg_json_next(struct mg_str obj, size_t ofs, struct mg_str *key, + struct mg_str *val); diff --git a/test/unit_test.c b/test/unit_test.c index c77e8560..ea928d78 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -2787,6 +2787,22 @@ static void test_get_header_var(void) { ASSERT(mg_strcmp(yy, mg_http_get_header_var(header, mg_str("x"))) == 0); } +static void json_scan(struct mg_str json, int depth) { + int i, n = 0, o = mg_json_get(json, "$", &n); + for (i = 0; i < depth; i++) printf(" "); + printf("%.*s\n", n, json.ptr + o); + if (json.ptr[o] == '{' || json.ptr[o] == '[') { // Iterate over elems + struct mg_str key, val, sub = mg_str_n(json.ptr + o, (size_t) n); + size_t ofs = 0; + while ((ofs = mg_json_next(sub, ofs, &key, &val)) > 0) { + for (i = 0; i < depth; i++) printf(" "); + printf("KEY: %.*s VAL: %.*s\n", (int) key.len, key.ptr, (int) val.len, + val.ptr); + if (*val.ptr == '[' || *val.ptr == '{') json_scan(val, depth + 1); + } + } +} + static void test_json(void) { const char *s1 = "{\"a\":{},\"b\":7,\"c\":[[],2]}"; const char *s2 = "{\"a\":{\"b1\":{}},\"c\":7,\"d\":{\"b2\":{}}}"; @@ -2968,6 +2984,21 @@ static void test_json(void) { ASSERT(mg_json_get_long(json, "$[0].a", -1) == -1); ASSERT(mg_json_get_long(json, "$[1].a", -1) == 2); ASSERT(mg_json_get_long(json, "$[2].a", -1) == -1); + + // mg_json_next() + json = mg_str("[1,true,{\"a\":[3],\"b\":42}]"); + json_scan(json, 0); + { + struct mg_str k, v, sub = mg_str_n(json.ptr + 8, json.len - 8); + const char *a = "\"a\"", *b = "\"b\""; + ASSERT(mg_json_next(sub, 0, &k, &v) == 9); + ASSERT(mg_vcmp(&k, a) == 0); + ASSERT(mg_vcmp(&v, "[3]") == 0); + ASSERT(mg_json_next(sub, 9, &k, &v) == 15); + ASSERT(mg_vcmp(&k, b) == 0); + ASSERT(mg_vcmp(&v, "42") == 0); + ASSERT(mg_json_next(sub, 15, &k, &v) == 0); + } } static void resp_rpc(struct mg_rpc_req *r) {