]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib: Added json-tree API for parsing JSON input into a tree structure.
authorTimo Sirainen <tss@iki.fi>
Wed, 8 Apr 2015 04:55:35 +0000 (13:55 +0900)
committerTimo Sirainen <tss@iki.fi>
Wed, 8 Apr 2015 04:55:35 +0000 (13:55 +0900)
This makes it easier to access complicated JSON structs that can fit into
memory.

src/lib/Makefile.am
src/lib/json-tree.c [new file with mode: 0644]
src/lib/json-tree.h [new file with mode: 0644]
src/lib/test-json-tree.c [new file with mode: 0644]
src/lib/test-lib.c
src/lib/test-lib.h

index 024ef09008aea53c8911045fc4b3425717b7d18c..40ec620168ac85a3093317b1c652537bb7efed83 100644 (file)
@@ -86,6 +86,7 @@ liblib_la_SOURCES = \
        ioloop-epoll.c \
        ioloop-kqueue.c \
        json-parser.c \
+       json-tree.c \
        lib.c \
        lib-signals.c \
        md4.c \
@@ -212,6 +213,7 @@ headers = \
        ioloop-private.h \
        ioloop-notify-fd.h \
        json-parser.h \
+       json-tree.h \
        lib.h \
        lib-signals.h \
        llist.h \
@@ -301,6 +303,7 @@ test_lib_SOURCES = \
        test-istream-seekable.c \
        test-istream-tee.c \
        test-json-parser.c \
+       test-json-tree.c \
        test-llist.c \
        test-mempool-alloconly.c \
        test-network.c \
diff --git a/src/lib/json-tree.c b/src/lib/json-tree.c
new file mode 100644 (file)
index 0000000..0b39bdf
--- /dev/null
@@ -0,0 +1,167 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "json-tree.h"
+
+struct json_tree {
+       pool_t pool;
+       struct json_tree_node *root, *cur, *cur_child;
+};
+
+struct json_tree *json_tree_init(void)
+{
+       struct json_tree *tree;
+       pool_t pool;
+
+       pool = pool_alloconly_create("json tree", 1024);
+       tree = p_new(pool, struct json_tree, 1);
+       tree->pool = pool;
+       tree->root = tree->cur = p_new(tree->pool, struct json_tree_node, 1);
+       tree->cur->value_type = JSON_TYPE_OBJECT;
+       return tree;
+}
+
+void json_tree_deinit(struct json_tree **_tree)
+{
+       struct json_tree *tree = *_tree;
+
+       *_tree = NULL;
+       pool_unref(&tree->pool);
+}
+
+static void
+json_tree_append_child(struct json_tree *tree, enum json_type type,
+                      const char *value)
+{
+       struct json_tree_node *node;
+
+       node = p_new(tree->pool, struct json_tree_node, 1);
+       node->parent = tree->cur;
+       node->value_type = type;
+       node->value.str = p_strdup(tree->pool, value);
+
+       if (tree->cur_child == NULL)
+               tree->cur->value.child = node;
+       else
+               tree->cur_child->next = node;
+       tree->cur_child = node;
+}
+
+static void
+json_tree_set_cur(struct json_tree *tree, struct json_tree_node *node)
+{
+       tree->cur = node;
+       tree->cur_child = tree->cur->value.child;
+       if (tree->cur_child != NULL) {
+               while (tree->cur_child->next != NULL)
+                       tree->cur_child = tree->cur_child->next;
+       }
+}
+
+static int
+json_tree_append_value(struct json_tree *tree, enum json_type type,
+                      const char *value)
+{
+       switch (tree->cur->value_type) {
+       case JSON_TYPE_OBJECT_KEY:
+               /* "key": value - we already added the node and set its key,
+                  so now just set the value */
+               tree->cur->value_type = type;
+               tree->cur->value.str = p_strdup(tree->pool, value);
+               json_tree_set_cur(tree, tree->cur->parent);
+               break;
+       case JSON_TYPE_ARRAY:
+               /* element in array - add a new node */
+               json_tree_append_child(tree, type, value);
+               break;
+       default:
+               return -1;
+       }
+       return 0;
+}
+
+int json_tree_append(struct json_tree *tree, enum json_type type,
+                    const char *value)
+{
+       switch (type) {
+       case JSON_TYPE_OBJECT_KEY:
+               if (tree->cur->value_type != JSON_TYPE_OBJECT)
+                       return -1;
+               json_tree_append_child(tree, type, NULL);
+               json_tree_set_cur(tree, tree->cur_child);
+               tree->cur->key = p_strdup(tree->pool, value);
+               break;
+       case JSON_TYPE_ARRAY:
+               if (json_tree_append_value(tree, type, NULL) < 0)
+                       return -1;
+               json_tree_set_cur(tree, tree->cur_child);
+               break;
+       case JSON_TYPE_OBJECT:
+               if (tree->cur->value_type == JSON_TYPE_OBJECT_KEY)
+                       tree->cur->value_type = JSON_TYPE_OBJECT;
+               else if (tree->cur->value_type == JSON_TYPE_ARRAY) {
+                       json_tree_append_child(tree, type, NULL);
+                       json_tree_set_cur(tree, tree->cur_child);
+               } else {
+                       return -1;
+               }
+               break;
+       case JSON_TYPE_OBJECT_END:
+               if (tree->cur->parent == NULL ||
+                   tree->cur->value_type != JSON_TYPE_OBJECT)
+                       return -1;
+               json_tree_set_cur(tree, tree->cur->parent);
+               break;
+       case JSON_TYPE_ARRAY_END:
+               if (tree->cur->parent == NULL ||
+                   tree->cur->value_type != JSON_TYPE_ARRAY)
+                       return -1;
+               json_tree_set_cur(tree, tree->cur->parent);
+               break;
+       case JSON_TYPE_STRING:
+       case JSON_TYPE_NUMBER:
+       case JSON_TYPE_TRUE:
+       case JSON_TYPE_FALSE:
+       case JSON_TYPE_NULL:
+               if (json_tree_append_value(tree, type, value) < 0)
+                       return -1;
+               break;
+       }
+       return 0;
+}
+
+struct json_tree_node *json_tree_root(struct json_tree *tree)
+{
+       return tree->root;
+}
+
+struct json_tree_node *
+json_tree_find_key(struct json_tree_node *node, const char *key)
+{
+       for (; node != NULL; node = node->next) {
+               if (node->key != NULL && strcmp(node->key, key) == 0)
+                       return node;
+       }
+       return NULL;
+}
+
+struct json_tree_node *
+json_tree_find_child_with(struct json_tree_node *node,
+                         const char *key, const char *value)
+{
+       struct json_tree_node *child;
+
+       i_assert(node->value_type == JSON_TYPE_OBJECT ||
+                node->value_type == JSON_TYPE_ARRAY);
+
+       for (node = node->value.child; node != NULL; node = node->next) {
+               if (node->value_type != JSON_TYPE_OBJECT)
+                       continue;
+
+               child = json_tree_find_key(node->value.child, key);
+               if (child != NULL && child->value.str != NULL &&
+                   strcmp(child->value.str, value) == 0)
+                       return node;
+       }
+       return NULL;
+}
diff --git a/src/lib/json-tree.h b/src/lib/json-tree.h
new file mode 100644 (file)
index 0000000..980d27a
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef JSON_TREE_H
+#define JSON_TREE_H
+
+#include "json-parser.h"
+
+struct json_tree_node {
+       /* object key, or NULL if we're in a list */
+       const char *key;
+       struct json_tree_node *parent, *next;
+
+       enum json_type value_type;
+       struct {
+               /* for JSON_TYPE_OBJECT and JSON_TYPE_ARRAY */
+               struct json_tree_node *child;
+               /* for other types */
+               const char *str;
+       } value;
+};
+
+struct json_tree *json_tree_init(void);
+void json_tree_deinit(struct json_tree **tree);
+
+/* Append data to a tree. The type/value should normally come from json-parser.
+   Returns 0 on success, -1 if the input was invalid (which should never happen
+   if it's coming from json-parser). */
+int json_tree_append(struct json_tree *tree, enum json_type type,
+                    const char *value);
+
+/* Return the root node. */
+struct json_tree_node *json_tree_root(struct json_tree *tree);
+/* Find a node with the specified key (from node's siblings) */
+struct json_tree_node *
+json_tree_find_key(struct json_tree_node *node, const char *key);
+/* Find an object node (from an array), which contains the specified key=value
+   in its values. */
+struct json_tree_node *
+json_tree_find_child_with(struct json_tree_node *node,
+                         const char *key, const char *value);
+
+#endif
diff --git a/src/lib/test-json-tree.c b/src/lib/test-json-tree.c
new file mode 100644 (file)
index 0000000..1c81fb7
--- /dev/null
@@ -0,0 +1,114 @@
+/* Copyright (c) 2015 Dovecot authors, see the included COPYING file */
+
+#include "test-lib.h"
+#include "json-tree.h"
+
+struct {
+       enum json_type type;
+       const char *value;
+} test_input[] = {
+       { JSON_TYPE_OBJECT_KEY, "key-str" },
+       { JSON_TYPE_STRING, "string" },
+       { JSON_TYPE_OBJECT_KEY, "key-num" },
+       { JSON_TYPE_NUMBER, "1234" },
+       { JSON_TYPE_OBJECT_KEY, "key-true" },
+       { JSON_TYPE_TRUE, "true" },
+       { JSON_TYPE_OBJECT_KEY, "key-false" },
+       { JSON_TYPE_FALSE, "false" },
+       { JSON_TYPE_OBJECT_KEY, "key-null" },
+       { JSON_TYPE_NULL, NULL },
+
+       { JSON_TYPE_OBJECT_KEY, "key-obj-empty" },
+       { JSON_TYPE_OBJECT, NULL },
+       { JSON_TYPE_OBJECT_END, NULL },
+
+       { JSON_TYPE_OBJECT_KEY, "key-obj" },
+       { JSON_TYPE_OBJECT, NULL },
+         { JSON_TYPE_OBJECT_KEY, "sub" },
+         { JSON_TYPE_STRING, "value" },
+       { JSON_TYPE_OBJECT_END, NULL },
+
+       { JSON_TYPE_OBJECT_KEY, "key-arr-empty" },
+       { JSON_TYPE_ARRAY, NULL },
+       { JSON_TYPE_ARRAY_END, NULL },
+
+       { JSON_TYPE_OBJECT_KEY, "key-arr" },
+       { JSON_TYPE_ARRAY, NULL },
+         { JSON_TYPE_STRING, "foo" },
+         { JSON_TYPE_ARRAY, NULL },
+         { JSON_TYPE_TRUE, "true" },
+         { JSON_TYPE_ARRAY_END, NULL },
+         { JSON_TYPE_OBJECT, NULL },
+           { JSON_TYPE_OBJECT_KEY, "aobj" },
+           { JSON_TYPE_ARRAY, NULL },
+           { JSON_TYPE_ARRAY_END, NULL },
+         { JSON_TYPE_OBJECT_END, NULL },
+         { JSON_TYPE_OBJECT, NULL },
+           { JSON_TYPE_OBJECT_KEY, "aobj-key" },
+           { JSON_TYPE_STRING, "value1" },
+         { JSON_TYPE_OBJECT_END, NULL },
+         { JSON_TYPE_OBJECT, NULL },
+           { JSON_TYPE_OBJECT_KEY, "aobj-key" },
+           { JSON_TYPE_STRING, "value2" },
+         { JSON_TYPE_OBJECT_END, NULL },
+       { JSON_TYPE_ARRAY_END, NULL }
+};
+
+void test_json_tree(void)
+{
+       struct json_tree *tree;
+       struct json_tree_node *root, *node, *node1, *node2;
+       unsigned int i;
+
+       test_begin("json tree");
+       tree = json_tree_init();
+       for (i = 0; i < N_ELEMENTS(test_input); i++) {
+               test_assert_idx(json_tree_append(tree, test_input[i].type,
+                                                test_input[i].value) == 0, i);
+       }
+
+       root = json_tree_root(tree);
+       i_assert(root != NULL);
+       test_assert(root->value_type == JSON_TYPE_OBJECT);
+       root = root->value.child;
+       i_assert(root != NULL);
+
+       for (i = 0; i < 10+2; i += 2) {
+               node = json_tree_find_key(root, test_input[i].value);
+               test_assert(node != NULL &&
+                           node->value_type == test_input[i+1].type &&
+                           null_strcmp(node->value.str, test_input[i+1].value) == 0);
+       }
+
+       node = json_tree_find_key(root, "key-obj");
+       test_assert(node != NULL);
+
+       node = json_tree_find_key(root, "key-arr-empty");
+       test_assert(node != NULL && node->value_type == JSON_TYPE_ARRAY &&
+                   node->value.child == NULL);
+
+       node = json_tree_find_key(root, "key-arr");
+       test_assert(node != NULL && node->value_type == JSON_TYPE_ARRAY);
+       node = node->value.child;
+       test_assert(node != NULL && node->value_type == JSON_TYPE_STRING &&
+                   strcmp(node->value.str, "foo") == 0);
+       node = node->next;
+       test_assert(node != NULL && node->value_type == JSON_TYPE_ARRAY &&
+                   node->value.child != NULL &&
+                   node->value.child->next == NULL &&
+                   node->value.child->value_type == JSON_TYPE_TRUE);
+       node = node->next;
+       test_assert(node != NULL && node->value_type == JSON_TYPE_OBJECT &&
+                   node->value.child != NULL &&
+                   node->value.child->next == NULL &&
+                   node->value.child->value_type == JSON_TYPE_ARRAY &&
+                   node->value.child->value.child == NULL);
+
+       node1 = json_tree_find_child_with(node->parent, "aobj-key", "value1");
+       node2 = json_tree_find_child_with(node->parent, "aobj-key", "value2");
+       test_assert(node1 != NULL && node2 != NULL && node1 != node2);
+       test_assert(json_tree_find_child_with(node->parent, "aobj-key", "value3") == NULL);
+
+       json_tree_deinit(&tree);
+       test_end();
+}
index 82a5364c781e3055e45b9138474d2e985441464e..99404cb707c1b02e63b919b1a900f97448568b71 100644 (file)
@@ -28,6 +28,7 @@ int main(void)
                test_istream_seekable,
                test_istream_tee,
                test_json_parser,
+               test_json_tree,
                test_llist,
                test_mempool_alloconly,
                test_network,
index 5eea844328381f42c48778ee0258a73b1cc8cbeb..3abde0ac7ea3d2e1e0e2d4b2e7801cbf54f2c77a 100644 (file)
@@ -29,6 +29,7 @@ void test_istream_crlf(void);
 void test_istream_seekable(void);
 void test_istream_tee(void);
 void test_json_parser(void);
+void test_json_tree(void);
 void test_llist(void);
 void test_mempool_alloconly(void);
 enum fatal_test_state fatal_mempool(int);