Skip to content

Commit

Permalink
Add mg_json_next() for iterating objects/arrays
Browse files Browse the repository at this point in the history
  • Loading branch information
cpq committed Oct 7, 2023
1 parent c29ee76 commit bb5e9f5
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 4 deletions.
53 changes: 51 additions & 2 deletions mongoose.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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] == '.' ||
Expand Down
2 changes: 2 additions & 0 deletions mongoose.h
Original file line number Diff line number Diff line change
Expand Up @@ -1625,6 +1625,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);



Expand Down
53 changes: 51 additions & 2 deletions src/json.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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] == '.' ||
Expand Down
2 changes: 2 additions & 0 deletions src/json.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
30 changes: 30 additions & 0 deletions test/unit_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -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\":{}}}";
Expand Down Expand Up @@ -2968,6 +2984,20 @@ 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);
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) {
Expand Down

0 comments on commit bb5e9f5

Please sign in to comment.