]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-json: json-tree - Implement tree walker
authorStephan Bosch <stephan.bosch@open-xchange.com>
Mon, 31 Jul 2023 10:50:37 +0000 (12:50 +0200)
committeraki.tuomi <aki.tuomi@open-xchange.com>
Sat, 18 Nov 2023 18:58:04 +0000 (18:58 +0000)
src/lib-json/Makefile.am
src/lib-json/json-tree.new.c
src/lib-json/json-tree.new.h
src/lib-json/test-json-tree.c [new file with mode: 0644]

index 2ce0ec99af0069ebd6e48195e69b615261cefb8d..bcc815ae23a43dffb2d4b3957c429c470453ce17 100644 (file)
@@ -28,7 +28,8 @@ test_programs = \
        test-json-generator \
        test-json-io \
        test-json-istream \
-       test-json-ostream
+       test-json-ostream \
+       test-json-tree
 
 noinst_PROGRAMS = $(test_programs)
 
@@ -79,6 +80,13 @@ test_json_ostream_LDADD = \
 test_json_ostream_DEPENDENCIES = \
        $(test_deps)
 
+test_json_tree_SOURCE = \
+       test-json-tree.c
+test_json_tree_LDADD = \
+       $(test_libs)
+test_json_tree_DEPENDENCIES = \
+       $(test_deps)
+
 pkginc_libdir=$(pkgincludedir)
 pkginc_lib_HEADERS = $(headers)
 
index c3e33d1976d2cdc7eaa129d10123d57fbc3498fe..2728232a54687e06018d95cda9808d571e24a744 100644 (file)
@@ -605,3 +605,145 @@ json_tree_node_get_child_with(const struct json_tree_node *jtnode,
 
        return child;
 }
+
+/*
+ * Walker
+ */
+
+struct json_tree_walker {
+       const struct json_tree_node *root, *node;
+       ARRAY_TYPE(json_tree_node_const) sub_nodes;
+       unsigned int node_level;
+
+       bool node_is_end:1;
+};
+
+struct json_tree_walker *
+json_tree_walker_create_from_node(const struct json_tree_node *tree_node)
+{
+       struct json_tree_walker *twalker;
+
+       i_assert(tree_node != NULL);
+
+       twalker = i_new(struct json_tree_walker, 1);
+       twalker->root = tree_node;
+
+       return twalker;
+}
+
+struct json_tree_walker *
+json_tree_walker_create(const struct json_tree *tree)
+{
+       i_assert(tree != NULL);
+       return json_tree_walker_create_from_node(
+               json_tree_get_root_const(tree));
+}
+
+void json_tree_walker_free(struct json_tree_walker **_twalker)
+{
+       struct json_tree_walker *twalker = *_twalker;
+
+       if (twalker == NULL)
+               return;
+       *_twalker = NULL;
+
+       array_free(&twalker->sub_nodes);
+       i_free(twalker);
+}
+
+static const struct json_tree_node *
+json_tree_walk_next(struct json_tree_walker *twalker, bool *is_end_r)
+{
+       const struct json_tree_node *tnode = twalker->node, *tnode_next;
+
+       *is_end_r = FALSE;
+
+       if (tnode == NULL) {
+               i_assert(twalker->node_level == 0);
+               twalker->node_level++;
+               return twalker->root;
+       }
+
+       bool tnode_is_end = twalker->node_is_end;
+       const struct json_node *node = &tnode->node;
+
+       if (!json_node_is_singular(node) && !tnode_is_end) {
+               tnode_next = json_tree_node_get_child(tnode);
+               if (tnode_next != NULL) {
+                       twalker->node_level++;
+                       return tnode_next;
+               }
+               *is_end_r = TRUE;
+               return tnode;
+       }
+
+       tnode_next = json_tree_node_get_next(tnode);
+       if (tnode_next != NULL || twalker->node_level == 0)
+               return tnode_next;
+
+       twalker->node_level--;
+       *is_end_r = TRUE;
+       return json_tree_node_get_parent(tnode);
+}
+
+bool json_tree_walk(struct json_tree_walker *twalker, struct json_node *node_r)
+{
+       const struct json_tree_node *tnode_next;
+       bool tnode_next_is_end;
+
+       tnode_next = json_tree_walk_next(twalker, &tnode_next_is_end);
+       if (tnode_next == NULL) {
+               i_assert(twalker->node_level == 0);
+               i_zero(node_r);
+               twalker->node = twalker->root = NULL;
+               twalker->node_is_end = TRUE;
+               return FALSE;
+       }
+       if (json_tree_node_is_root(tnode_next) && twalker->node_level > 1) {
+               const struct json_tree_node *tnode_sub = tnode_next;
+
+               /* Returned to root of subtree */
+               i_assert(tnode_next_is_end);
+               i_assert(array_is_created(&twalker->sub_nodes));
+               i_assert(array_count(&twalker->sub_nodes) > 0);
+               tnode_next = *array_back(&twalker->sub_nodes);
+               array_pop_back(&twalker->sub_nodes);
+
+               i_zero(node_r);
+               node_r->name = tnode_next->node.name;
+               node_r->type = tnode_sub->node.type;
+
+               twalker->node = tnode_next;
+               twalker->node_is_end = TRUE;
+               return TRUE;
+       }
+
+       const struct json_node *node_next = &tnode_next->node;
+
+       if (tnode_next_is_end) {
+               i_zero(node_r);
+               node_r->name = node_next->name;
+               node_r->type = node_next->type;
+       } else if (node_next->type == JSON_TYPE_TEXT &&
+                  node_next->value.content_type == JSON_CONTENT_TYPE_TREE) {
+               struct json_tree *tree = node_next->value.content.tree;
+               const struct json_tree_node *tnode_sub;
+
+               /* Descend into subtree */
+               if (!array_is_created(&twalker->sub_nodes))
+                       i_array_init(&twalker->sub_nodes, 4);
+               array_push_back(&twalker->sub_nodes, &tnode_next);
+               tnode_sub = json_tree_get_root(tree);
+               i_assert(tnode_sub != NULL);
+               i_assert(tnode_sub->node.type != JSON_TYPE_NONE);
+               *node_r = tnode_sub->node;
+               node_r->name = node_next->name;
+               tnode_next = tnode_sub;
+       } else {
+               *node_r = tnode_next->node;
+       }
+
+       twalker->node = tnode_next;
+       twalker->node_is_end = tnode_next_is_end;
+       return TRUE;
+}
index 873319b12f4d01b600fcb4f2b535a72d98a43e15..0a6eb371f25ba0a00b78e4035442a82369ea6e0e 100644 (file)
@@ -254,4 +254,18 @@ bool json_tree_is_false(const struct json_tree *jtree) ATTR_PURE;
 bool json_tree_is_boolean(const struct json_tree *jtree) ATTR_PURE;
 bool json_tree_is_null(const struct json_tree *jtree) ATTR_PURE;
 
+/*
+ * Walker
+ */
+
+struct json_tree_walker;
+
+struct json_tree_walker *
+json_tree_walker_create_from_node(const struct json_tree_node *tree_node);
+struct json_tree_walker *
+json_tree_walker_create(const struct json_tree *tree);
+void json_tree_walker_free(struct json_tree_walker **_twalker);
+
+bool json_tree_walk(struct json_tree_walker *twalker, struct json_node *node_r);
+
 #endif
diff --git a/src/lib-json/test-json-tree.c b/src/lib-json/test-json-tree.c
new file mode 100644 (file)
index 0000000..63c6ccd
--- /dev/null
@@ -0,0 +1,518 @@
+/* Copyright (c) 2017-2023 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "istream.h"
+#include "ostream.h"
+#include "test-common.h"
+
+#include "json-tree.new.h"
+
+#include <unistd.h>
+
+static bool debug = FALSE;
+
+static void test_stream_value(struct istream *val_input, const char *expected)
+{
+       const unsigned char *data;
+       size_t size;
+       string_t *buffer;
+       int ret;
+
+       buffer = str_new(default_pool, 256);
+
+       while ((ret = i_stream_read_more(val_input, &data, &size)) > 0) {
+               str_append_data(buffer, data, size);
+               i_stream_skip(val_input, size);
+       }
+       if (ret < 0)
+               test_assert(!i_stream_have_bytes_left(val_input));
+
+       test_assert_strcmp(str_c(buffer), expected);
+       str_free(&buffer);
+}
+
+static void test_json_tree_walker(void)
+{
+       struct istream *input;
+       const char *data;
+       struct json_tree *jtree, *jtree2, *jtree3;
+       struct json_tree_node *jtnode;
+       struct json_tree_walker *jtwalker;
+       struct json_node jnode;
+       intmax_t num_val = 0;
+
+       /* number */
+       test_begin("json tree walker - number");
+       jtree = json_tree_create();
+       (void)json_tree_node_add_number_int(
+               json_tree_get_root(jtree), NULL, 23423);
+       jtwalker = json_tree_walker_create(jtree);
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_number(&jnode));
+       test_assert(json_node_get_intmax(&jnode, &num_val) == 0);
+       test_assert(num_val == 23423);
+       test_assert(!json_tree_walk(jtwalker, &jnode));
+       json_tree_walker_free(&jtwalker);
+       json_tree_unref(&jtree);
+       test_end();
+
+       /* false */
+       test_begin("json tree walker - false");
+       jtree = json_tree_create();
+       (void)json_tree_node_add_false(
+               json_tree_get_root(jtree), NULL);
+       jtwalker = json_tree_walker_create(jtree);
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_false(&jnode));
+       test_assert(!json_tree_walk(jtwalker, &jnode));
+       json_tree_walker_free(&jtwalker);
+       json_tree_unref(&jtree);
+       test_end();
+
+       /* null */
+       test_begin("json tree walker - null");
+       jtree = json_tree_create();
+       (void)json_tree_node_add_null(
+               json_tree_get_root(jtree), NULL);
+       jtwalker = json_tree_walker_create(jtree);
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_null(&jnode));
+       test_assert(!json_tree_walk(jtwalker, &jnode));
+       json_tree_walker_free(&jtwalker);
+       json_tree_unref(&jtree);
+       test_end();
+
+       /* true */
+       test_begin("json tree walker - true");
+       jtree = json_tree_create();
+       (void)json_tree_node_add_true(
+               json_tree_get_root(jtree), NULL);
+       jtwalker = json_tree_walker_create(jtree);
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_true(&jnode));
+       test_assert(!json_tree_walk(jtwalker, &jnode));
+       json_tree_walker_free(&jtwalker);
+       json_tree_unref(&jtree);
+       test_end();
+
+       /* string */
+       test_begin("json tree walker - string");
+       jtree = json_tree_create();
+       (void)json_tree_node_add_string(
+               json_tree_get_root(jtree), NULL, "frop");
+       jtwalker = json_tree_walker_create(jtree);
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_string(&jnode));
+       test_assert_strcmp(json_node_get_str(&jnode), "frop");
+       test_assert(!json_tree_walk(jtwalker, &jnode));
+       json_tree_walker_free(&jtwalker);
+       json_tree_unref(&jtree);
+       test_end();
+
+       /* string stream */
+       test_begin("json tree walker - string stream");
+       jtree = json_tree_create();
+       data = "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ";
+       input = i_stream_create_from_data(data, strlen(data));
+       (void)json_tree_node_add_string_stream(
+               json_tree_get_root(jtree), NULL, input);
+       i_stream_unref(&input);
+       jtwalker = json_tree_walker_create(jtree);
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_string(&jnode));
+       test_assert(jnode.value.content_type == JSON_CONTENT_TYPE_STREAM);
+       test_assert(jnode.value.content.stream != NULL);
+       test_stream_value(jnode.value.content.stream, data);
+       test_assert(!json_tree_walk(jtwalker, &jnode));
+       json_tree_walker_free(&jtwalker);
+       json_tree_unref(&jtree);
+       test_end();
+
+       /* array */
+       test_begin("json tree walker - array");
+       jtree = json_tree_create();
+       (void)json_tree_node_add_array(
+               json_tree_get_root(jtree), NULL);
+       jtwalker = json_tree_walker_create(jtree);
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_array(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_array_end(&jnode));
+       test_assert(!json_tree_walk(jtwalker, &jnode));
+       json_tree_walker_free(&jtwalker);
+       json_tree_unref(&jtree);
+       test_end();
+
+       /* [ string ] */
+       test_begin("json tree walker - array [ string ]");
+       jtree = json_tree_create();
+       jtnode = json_tree_node_add_array(
+               json_tree_get_root(jtree), NULL);
+       (void)json_tree_node_add_string(jtnode, NULL, "frop");
+       jtwalker = json_tree_walker_create(jtree);
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_array(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_string(&jnode));
+       test_assert_strcmp(json_node_get_str(&jnode), "frop");
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_array_end(&jnode));
+       test_assert(!json_tree_walk(jtwalker, &jnode));
+       json_tree_walker_free(&jtwalker);
+       json_tree_unref(&jtree);
+       test_end();
+
+       /* [ string stream ] */
+       test_begin("json tree walker - array [ string stream ]");
+       jtree = json_tree_create();
+       jtnode = json_tree_node_add_array(
+               json_tree_get_root(jtree), NULL);
+       data = "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ";
+       input = i_stream_create_from_data(data, strlen(data));
+       (void)json_tree_node_add_string_stream(jtnode, NULL, input);
+       i_stream_unref(&input);
+       jtwalker = json_tree_walker_create(jtree);
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_array(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_string(&jnode));
+       test_assert(jnode.value.content_type == JSON_CONTENT_TYPE_STREAM);
+       test_assert(jnode.value.content.stream != NULL);
+       test_stream_value(jnode.value.content.stream, data);
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_array_end(&jnode));
+       test_assert(!json_tree_walk(jtwalker, &jnode));
+       json_tree_walker_free(&jtwalker);
+       json_tree_unref(&jtree);
+       test_end();
+
+       /* object */
+       test_begin("json tree walker - object");
+       jtree = json_tree_create();
+       (void)json_tree_node_add_object(
+               json_tree_get_root(jtree), NULL);
+       jtwalker = json_tree_walker_create(jtree);
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_object(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_object_end(&jnode));
+       test_assert(!json_tree_walk(jtwalker, &jnode));
+       json_tree_unref(&jtree);
+       json_tree_walker_free(&jtwalker);
+       test_end();
+
+       /* { member: string } */
+       test_begin("json tree walker - object { member: string }");
+       jtree = json_tree_create();
+       jtnode = json_tree_node_add_object(
+               json_tree_get_root(jtree), NULL);
+       (void)json_tree_node_add_string(jtnode, "frop", "friep");
+       jtwalker = json_tree_walker_create(jtree);
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_object(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_string(&jnode));
+       test_assert_strcmp(jnode.name, "frop");
+       test_assert_strcmp(json_node_get_str(&jnode), "friep");
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_object_end(&jnode));
+       test_assert(!json_tree_walk(jtwalker, &jnode));
+       json_tree_unref(&jtree);
+       json_tree_walker_free(&jtwalker);
+       test_end();
+
+       /* { member: string stream } */
+       test_begin("json tree walker - object { member: string stream }");
+       jtree = json_tree_create();
+       jtnode = json_tree_node_add_object(
+               json_tree_get_root(jtree), NULL);
+       data = "AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ";
+       input = i_stream_create_from_data(data, strlen(data));
+       (void)json_tree_node_add_string_stream(jtnode, "frop", input);
+       i_stream_unref(&input);
+       jtwalker = json_tree_walker_create(jtree);
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_object(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_string(&jnode));
+       test_assert_strcmp(jnode.name, "frop");
+       test_assert(jnode.value.content_type == JSON_CONTENT_TYPE_STREAM);
+       test_assert(jnode.value.content.stream != NULL);
+       test_stream_value(jnode.value.content.stream, data);
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_object_end(&jnode));
+       test_assert(!json_tree_walk(jtwalker, &jnode));
+       json_tree_walker_free(&jtwalker);
+       json_tree_unref(&jtree);
+       test_end();
+
+       /* { "a": [{"d": 1}], "b": [{"e": 2}], "c": [{"f": 3}] } */
+       test_begin("json tree walker - object { \"a\": [{\"d\": 1}], \"b\": [{\"e\": 2}], \"c\": [{\"f\": 3}] }");
+       jtree = json_tree_create();
+       jtnode = json_tree_node_add_object(
+               json_tree_get_root(jtree), NULL);
+       jtnode = json_tree_node_add_array(jtnode, "a");
+       jtnode = json_tree_node_add_object(jtnode, NULL);
+       (void)json_tree_node_add_number_int(jtnode, "d", 1);
+       jtnode = json_tree_node_get_parent(jtnode);
+       jtnode = json_tree_node_get_parent(jtnode);
+       jtnode = json_tree_node_add_array(jtnode, "b");
+       jtnode = json_tree_node_add_object(jtnode, NULL);
+       (void)json_tree_node_add_number_int(jtnode, "e", 2);
+       jtnode = json_tree_node_get_parent(jtnode);
+       jtnode = json_tree_node_get_parent(jtnode);
+       jtnode = json_tree_node_add_array(jtnode, "c");
+       jtnode = json_tree_node_add_object(jtnode, NULL);
+       (void)json_tree_node_add_number_int(jtnode, "f", 3);
+       jtwalker = json_tree_walker_create(jtree);
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_object(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_array(&jnode));
+       test_assert_strcmp(jnode.name, "a");
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_object(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_number(&jnode));
+       test_assert_strcmp(jnode.name, "d");
+       test_assert(json_node_get_intmax(&jnode, &num_val) == 0);
+       test_assert(num_val == 1);
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_object_end(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_array_end(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_array(&jnode));
+       test_assert_strcmp(jnode.name, "b");
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_object(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_number(&jnode));
+       test_assert_strcmp(jnode.name, "e");
+       test_assert(json_node_get_intmax(&jnode, &num_val) == 0);
+       test_assert(num_val == 2);
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_object_end(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_array_end(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_array(&jnode));
+       test_assert_strcmp(jnode.name, "c");
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_object(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_number(&jnode));
+       test_assert_strcmp(jnode.name, "f");
+       test_assert(json_node_get_intmax(&jnode, &num_val) == 0);
+       test_assert(num_val == 3);
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_object_end(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_array_end(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_object_end(&jnode));
+       test_assert(!json_tree_walk(jtwalker, &jnode));
+       json_tree_unref(&jtree);
+       json_tree_walker_free(&jtwalker);
+       test_end();
+
+       /* { "a": [{"d": 1}], "b": [{"e": 2}], "c": [{"f": 3}] } */
+       test_begin("json tree walker - nested trees");
+       jtree = json_tree_create();
+       jtnode = json_tree_get_root(jtree);
+       jtnode = json_tree_node_add_object(jtnode, NULL);
+       jtree2 = json_tree_create();
+       jtnode = json_tree_get_root(jtree2);
+       jtnode = json_tree_node_add_array(jtnode, NULL);
+       jtnode = json_tree_node_add_object(jtnode, NULL);
+       (void)json_tree_node_add_number_int(jtnode, "d", 1);
+       jtnode = json_tree_get_root(jtree);
+       json_tree_node_add_subtree(jtnode, "a", jtree2);
+       json_tree_unref(&jtree2);
+       jtree2 = json_tree_create();
+       jtnode = json_tree_get_root(jtree2);
+       jtnode = json_tree_node_add_array(jtnode, NULL);
+       jtnode = json_tree_node_add_object(jtnode, NULL);
+       (void)json_tree_node_add_number_int(jtnode, "e", 2);
+       jtnode = json_tree_get_root(jtree);
+       json_tree_node_add_subtree(jtnode, "b", jtree2);
+       json_tree_unref(&jtree2);
+       jtree2 = json_tree_create();
+       jtnode = json_tree_get_root(jtree2);
+       jtnode = json_tree_node_add_array(jtnode, NULL);
+       jtnode = json_tree_node_add_object(jtnode, NULL);
+       (void)json_tree_node_add_number_int(jtnode, "f", 3);
+       jtnode = json_tree_get_root(jtree);
+       json_tree_node_add_subtree(jtnode, "c", jtree2);
+       json_tree_unref(&jtree2);
+       jtwalker = json_tree_walker_create(jtree);
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_object(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_array(&jnode));
+       test_assert_strcmp(jnode.name, "a");
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_object(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_number(&jnode));
+       test_assert_strcmp(jnode.name, "d");
+       test_assert(json_node_get_intmax(&jnode, &num_val) == 0);
+       test_assert(num_val == 1);
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_object_end(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_array_end(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_array(&jnode));
+       test_assert_strcmp(jnode.name, "b");
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_object(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_number(&jnode));
+       test_assert_strcmp(jnode.name, "e");
+       test_assert(json_node_get_intmax(&jnode, &num_val) == 0);
+       test_assert(num_val == 2);
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_object_end(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_array_end(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_array(&jnode));
+       test_assert_strcmp(jnode.name, "c");
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_object(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_number(&jnode));
+       test_assert_strcmp(jnode.name, "f");
+       test_assert(json_node_get_intmax(&jnode, &num_val) == 0);
+       test_assert(num_val == 3);
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_object_end(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_array_end(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_object_end(&jnode));
+       test_assert(!json_tree_walk(jtwalker, &jnode));
+       json_tree_walker_free(&jtwalker);
+       json_tree_unref(&jtree);
+       test_end();
+
+       /* { "a": [{"d": 1}], "b": [{"e": 2}], "c": [{"f": 3}] } */
+       test_begin("json tree walker - doubly nested trees");
+       jtree = json_tree_create();
+       jtnode = json_tree_get_root(jtree);
+       jtnode = json_tree_node_add_object(jtnode, NULL);
+       jtree2 = json_tree_create();
+       jtnode = json_tree_get_root(jtree2);
+       jtnode = json_tree_node_add_array(jtnode, NULL);
+       jtree3 = json_tree_create();
+       jtnode = json_tree_get_root(jtree3);
+       jtnode = json_tree_node_add_object(jtnode, NULL);
+       (void)json_tree_node_add_number_int(jtnode, "d", 1);
+       jtnode = json_tree_get_root(jtree2);
+       json_tree_node_add_subtree(jtnode, NULL, jtree3);
+       json_tree_unref(&jtree3);
+       jtnode = json_tree_get_root(jtree);
+       json_tree_node_add_subtree(jtnode, "a", jtree2);
+       json_tree_unref(&jtree2);
+       jtree2 = json_tree_create();
+       jtnode = json_tree_get_root(jtree2);
+       jtnode = json_tree_node_add_array(jtnode, NULL);
+       jtree3 = json_tree_create();
+       jtnode = json_tree_get_root(jtree3);
+       jtnode = json_tree_node_add_object(jtnode, NULL);
+       (void)json_tree_node_add_number_int(jtnode, "e", 2);
+       jtnode = json_tree_get_root(jtree2);
+       json_tree_node_add_subtree(jtnode, NULL, jtree3);
+       json_tree_unref(&jtree3);
+       jtnode = json_tree_get_root(jtree);
+       json_tree_node_add_subtree(jtnode, "b", jtree2);
+       json_tree_unref(&jtree2);
+       jtree2 = json_tree_create();
+       jtnode = json_tree_get_root(jtree2);
+       jtnode = json_tree_node_add_array(jtnode, NULL);
+       jtree3 = json_tree_create();
+       jtnode = json_tree_get_root(jtree3);
+       jtnode = json_tree_node_add_object(jtnode, NULL);
+       (void)json_tree_node_add_number_int(jtnode, "f", 3);
+       jtnode = json_tree_get_root(jtree2);
+       json_tree_node_add_subtree(jtnode, NULL, jtree3);
+       json_tree_unref(&jtree3);
+       jtnode = json_tree_get_root(jtree);
+       json_tree_node_add_subtree(jtnode, "c", jtree2);
+       json_tree_unref(&jtree2);
+       jtwalker = json_tree_walker_create(jtree);
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_object(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_array(&jnode));
+       test_assert_strcmp(jnode.name, "a");
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_object(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_number(&jnode));
+       test_assert_strcmp(jnode.name, "d");
+       test_assert(json_node_get_intmax(&jnode, &num_val) == 0);
+       test_assert(num_val == 1);
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_object_end(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_array_end(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_array(&jnode));
+       test_assert_strcmp(jnode.name, "b");
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_object(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_number(&jnode));
+       test_assert_strcmp(jnode.name, "e");
+       test_assert(json_node_get_intmax(&jnode, &num_val) == 0);
+       test_assert(num_val == 2);
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_object_end(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_array_end(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_array(&jnode));
+       test_assert_strcmp(jnode.name, "c");
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_object(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_number(&jnode));
+       test_assert_strcmp(jnode.name, "f");
+       test_assert(json_node_get_intmax(&jnode, &num_val) == 0);
+       test_assert(num_val == 3);
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_object_end(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_array_end(&jnode));
+       test_assert(json_tree_walk(jtwalker, &jnode));
+       test_assert(json_node_is_object_end(&jnode));
+       test_assert(!json_tree_walk(jtwalker, &jnode));
+       json_tree_walker_free(&jtwalker);
+       json_tree_unref(&jtree);
+       test_end();
+}
+
+int main(int argc, char *argv[])
+{
+       int c;
+
+       static void (*test_functions[])(void) = {
+               test_json_tree_walker,
+               NULL
+       };
+
+       while ((c = getopt(argc, argv, "D")) > 0) {
+               switch (c) {
+               case 'D':
+                       debug = TRUE;
+                       break;
+               default:
+                       i_fatal("Usage: %s [-D]", argv[0]);
+               }
+       }
+
+       return test_run(test_functions);
+}