Browse Source

Implement json_next_key(), json_next_elem()

PUBLISHED_FROM=27e4ae61d38f9236df5cd91d56dc6912176b4dea
Бобби 8 years ago
parent
commit
91ac1644c7
4 changed files with 174 additions and 0 deletions
  1. 32 0
      README.md
  2. 77 0
      frozen.c
  3. 27 0
      frozen.h
  4. 38 0
      unit_test.c

+ 32 - 0
README.md

@@ -348,6 +348,38 @@ int json_prettify(const char *s, int len, struct json_out *out);
 int json_prettify_file(const char *file_name);
 ```
 
+## `json_next_key()`, `json_next_elem()`
+
+```c
+/*
+ * Iterate over an object at given JSON `path`.
+ * On each iteration, fill the `key` and `val` tokens. It is OK to pass NULL
+ * for `key`, or `val`, in which case they won't be populated.
+ * Return an opaque value suitable for the next iteration, or NULL when done.
+ *
+ * Example:
+ *
+ * ```c
+ * void *h = NULL;
+ * struct json_token key, val;
+ * while ((h = json_next_key(s, len, h, ".foo", &key, &val)) != NULL) {
+ *   printf("[%.*s] -> [%.*s]\n", key.len, key.ptr, val.len, val.ptr);
+ * }
+ * ```
+ */
+void *json_next_key(const char *s, int len, void *handle, const char *path,
+                    struct json_token *key, struct json_token *val);
+
+
+/*
+ * Iterate over an array at given JSON `path`.
+ * Similar to `json_next_key`, but fills array index `idx` instead of `key`.
+ */
+void *json_next_elem(const char *s, int len, void *handle, const char *path,
+                     int *idx, struct json_token *val);
+
+```
+
 # Contributions
 
 To submit contributions, sign

+ 77 - 0
frozen.c

@@ -1280,3 +1280,80 @@ int json_prettify_file(const char *file_name) {
   free(s);
   return res;
 }
+
+struct next_data {
+  void *handle;            // Passed handle. Changed if a next entry is found
+  const char *path;        // Path to the iterated object/array
+  int path_len;            // Path length - optimisation
+  int found;               // Non-0 if found the next entry
+  struct json_token *key;  // Object's key
+  struct json_token *val;  // Object's value
+  int *idx;                // Array index
+};
+
+static void next_set_key(struct next_data *d, const char *name, int name_len,
+                         int is_array) {
+  if (is_array) {
+    /* Array. Set index and reset key  */
+    if (d->key != NULL) {
+      d->key->len = 0;
+      d->key->ptr = NULL;
+    }
+    if (d->idx != NULL) *d->idx = atoi(name);
+  } else {
+    /* Object. Set key and make index -1 */
+    if (d->key != NULL) {
+      d->key->ptr = name;
+      d->key->len = name_len;
+    }
+    if (d->idx != NULL) *d->idx = -1;
+  }
+}
+
+static void next_cb(void *userdata, const char *name, size_t name_len,
+                    const char *path, const struct json_token *t) {
+  struct next_data *d = (struct next_data *) userdata;
+  const char *p = path + d->path_len;
+  if (d->found) return;
+  if (d->path_len >= (int) strlen(path)) return;
+  if (strchr(p, '.') != NULL) return;     /* More nested objects - skip */
+  if (strchr(p + 1, '[') != NULL) return; /* Ditto for arrays */
+
+  // {OBJECT,ARRAY}_END types do not pass name, _START does. Save key.
+  if (t->type == JSON_TYPE_OBJECT_START || t->type == JSON_TYPE_ARRAY_START) {
+    // printf("SAV %s %d %p\n", path, t->type, t->ptr);
+    next_set_key(d, name, name_len, p[0] == '[');
+  } else if (d->handle == NULL || d->handle < (void *) t->ptr) {
+    // printf("END %s %d %p\n", path, t->type, t->ptr);
+    if (t->type != JSON_TYPE_OBJECT_END && t->type != JSON_TYPE_ARRAY_END) {
+      next_set_key(d, name, name_len, p[0] == '[');
+    }
+    if (d->val != NULL) *d->val = *t;
+    d->handle = (void *) t->ptr;
+    d->found = 1;
+  }
+}
+
+static void *json_next(const char *s, int len, void *handle, const char *path,
+                       struct json_token *key, struct json_token *val, int *i) {
+  struct json_token tmpval, *v = val == NULL ? &tmpval : val;
+  struct json_token tmpkey, *k = key == NULL ? &tmpkey : key;
+  int tmpidx, *pidx = i == NULL ? &tmpidx : i;
+  struct next_data data = {handle, path, strlen(path), 0, k, v, pidx};
+  json_walk(s, len, next_cb, &data);
+  return data.found ? data.handle : NULL;
+}
+
+void *json_next_key(const char *s, int len, void *handle, const char *path,
+                    struct json_token *key, struct json_token *val) WEAK;
+void *json_next_key(const char *s, int len, void *handle, const char *path,
+                    struct json_token *key, struct json_token *val) {
+  return json_next(s, len, handle, path, key, val, NULL);
+}
+
+void *json_next_elem(const char *s, int len, void *handle, const char *path,
+                     int *idx, struct json_token *val) WEAK;
+void *json_next_elem(const char *s, int len, void *handle, const char *path,
+                     int *idx, struct json_token *val) {
+  return json_next(s, len, handle, path, NULL, val, idx);
+}

+ 27 - 0
frozen.h

@@ -266,6 +266,33 @@ int json_prettify(const char *s, int len, struct json_out *out);
  */
 int json_prettify_file(const char *file_name);
 
+/*
+ * Iterate over an object at given JSON `path`.
+ * On each iteration, fill the `key` and `val` tokens. It is OK to pass NULL
+ * for `key`, or `val`, in which case they won't be populated.
+ * Return an opaque value suitable for the next iteration, or NULL when done.
+ *
+ * Example:
+ *
+ * ```c
+ * void *h = NULL;
+ * struct json_token key, val;
+ * while ((h = json_next_key(s, len, h, ".foo", &key, &val)) != NULL) {
+ *   printf("[%.*s] -> [%.*s]\n", key.len, key.ptr, val.len, val.ptr);
+ * }
+ * ```
+ */
+void *json_next_key(const char *s, int len, void *handle, const char *path,
+                    struct json_token *key, struct json_token *val);
+
+
+/*
+ * Iterate over an array at given JSON `path`.
+ * Similar to `json_next_key`, but fills array index `idx` instead of `key`.
+ */
+void *json_next_elem(const char *s, int len, void *handle, const char *path,
+                     int *idx, struct json_token *val);
+
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */

+ 38 - 0
unit_test.c

@@ -849,7 +849,45 @@ static const char *test_prettify(void) {
   return NULL;
 }
 
+static const char *test_json_next(void) {
+  const char *s = "{ \"a\": [], \"b\": [ 1, {} ], \"c\": true }";
+  struct json_token key, val;
+  char buf[100];
+  int len = strlen(s);
+
+  {
+    /* Traverse an object */
+    void *h = NULL;
+    int i = 0;
+    const char *results[] = {"[a] -> [[]]", "[b] -> [[ 1, {} ]]",
+                             "[c] -> [true]"};
+    while ((h = json_next_key(s, len, h, ".", &key, &val)) != NULL) {
+      snprintf(buf, sizeof(buf), "[%.*s] -> [%.*s]", key.len, key.ptr, val.len,
+               val.ptr);
+      ASSERT(strcmp(results[i], buf) == 0);
+      i++;
+    }
+    ASSERT(i == 3);
+  }
+
+  {
+    /* Traverse an array */
+    void *h = NULL;
+    int i = 0, idx;
+    const char *results[] = {"[0] -> [1]", "[1] -> [{}]"};
+    while ((h = json_next_elem(s, len, h, ".b", &idx, &val)) != NULL) {
+      snprintf(buf, sizeof(buf), "[%d] -> [%.*s]", idx, val.len, val.ptr);
+      ASSERT(strcmp(results[i], buf) == 0);
+      i++;
+    }
+    ASSERT(i == 2);
+  }
+
+  return NULL;
+}
+
 static const char *run_all_tests(void) {
+  RUN_TEST(test_json_next);
   RUN_TEST(test_prettify);
   RUN_TEST(test_eos);
   RUN_TEST(test_scanf);