]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-json: json-istream - Add support for reading JSON tree values
authorStephan Bosch <stephan.bosch@open-xchange.com>
Wed, 7 Aug 2019 19:48:01 +0000 (21:48 +0200)
committeraki.tuomi <aki.tuomi@open-xchange.com>
Sat, 18 Nov 2023 18:58:04 +0000 (18:58 +0000)
src/lib-json/json-istream.c
src/lib-json/json-istream.h
src/lib-json/test-json-istream.c

index f4017775a46c581ac6327b632732c38e4d8ed340..a3515946bee5c54473cf8251c8091eb6b4cb5f53 100644 (file)
@@ -20,6 +20,10 @@ struct json_istream {
 
        struct istream *value_stream, *seekable_stream;
 
+       struct json_tree *tree;
+       struct json_tree_node *tree_node;
+       unsigned int tree_node_level;
+
        char *error;
 
        bool opened:1;
@@ -223,11 +227,12 @@ static inline bool json_istream_parse_skip(struct json_istream *stream)
 }
 
 static void
-json_istream_parse_list_open(void *context, void *parent_context ATTR_UNUSED,
+json_istream_parse_list_open(void *context, void *parent_context,
                             const char *name, bool object,
-                            void **list_context_r ATTR_UNUSED)
+                            void **list_context_r)
 {
        struct json_istream *stream = context;
+       struct json_tree_node *parent = parent_context;
        unsigned int node_level = stream->node_level;
 
        i_assert(!stream->node_parsed);
@@ -259,6 +264,19 @@ json_istream_parse_list_open(void *context, void *parent_context ATTR_UNUSED,
 
        stream->node_level++;
 
+       if (stream->tree != NULL) {
+               if (parent == NULL)
+                       parent = stream->tree_node;
+               if (object) {
+                       *list_context_r = (void *)
+                               json_tree_node_add_object(parent, name);
+               } else {
+                       *list_context_r = (void *)
+                               json_tree_node_add_array(parent, name);
+               }
+               return;
+       }
+
        if (node_level == stream->read_node_level) {
                i_zero(&stream->node);
                stream->node.name = name;
@@ -307,6 +325,20 @@ json_istream_parse_list_close(void *context, void *list_context ATTR_UNUSED,
                }
        }
 
+       if (stream->tree != NULL) {
+               if (stream->node_level < stream->tree_node_level) {
+                       stream->end_of_list = TRUE;
+                       stream->node_parsed = TRUE;
+                       json_parser_interrupt(stream->parser);
+               } else if (stream->node_level == stream->tree_node_level) {
+                       if (!json_istream_parse_skip(stream)) {
+                               stream->node_parsed = TRUE;
+                               json_parser_interrupt(stream->parser);
+                       }
+               }
+               return;
+       }
+
        if (stream->node_level < stream->read_node_level) {
                stream->end_of_list = TRUE;
                if (!json_istream_parse_skip(stream)) {
@@ -334,6 +366,7 @@ json_istream_parse_object_member(void *context,
        if (stream->skip_to_end || stream->skip_nodes > 0)
                return;
 
+       i_assert(stream->tree == NULL);
        i_assert(stream->node_level >= stream->read_node_level);
 
        if (stream->node_level != stream->read_node_level)
@@ -346,11 +379,11 @@ json_istream_parse_object_member(void *context,
 }
 
 static void
-json_istream_parse_value(void *context, void *parent_context ATTR_UNUSED,
-                        const char *name, enum json_type type,
-                        const struct json_value *value)
+json_istream_parse_value(void *context, void *parent_context, const char *name,
+                        enum json_type type, const struct json_value *value)
 {
        struct json_istream *stream = context;
+       struct json_tree_node *parent = parent_context;
 
        i_assert(!stream->node_parsed);
        i_assert(stream->node_level >= stream->read_node_level);
@@ -374,6 +407,20 @@ json_istream_parse_value(void *context, void *parent_context ATTR_UNUSED,
                }
        }
 
+       if (stream->tree != NULL) {
+               if (parent == NULL) {
+                       /* just starting; parent is not in the syntax tree */
+                       parent = stream->tree_node;
+               }
+               (void)json_tree_node_add_value(parent, name, type, value);
+               if (stream->node_level == stream->tree_node_level) {
+                       stream->node_parsed = TRUE;
+                       json_parser_interrupt(stream->parser);
+               }
+               return;
+       }
+
+       /* not parsing a full tree */
        if (stream->node_level != stream->read_node_level)
                return;
        if (json_istream_parse_skip(stream))
@@ -409,6 +456,8 @@ static void json_istream_dereference_value(struct json_istream *stream)
                }
                json_parser_disable_string_stream(stream->parser);
        }
+       if (stream->tree != NULL)
+               json_tree_unref(&stream->tree);
 }
 
 int json_istream_read(struct json_istream *stream, struct json_node *node_r)
@@ -581,6 +630,8 @@ int json_istream_descend(struct json_istream *stream,
 
 static void json_istream_ascend_common(struct json_istream *stream)
 {
+       if (stream->tree != NULL)
+               json_tree_unref(&stream->tree);
        stream->skip_nodes = 0;
        stream->node_parsed = FALSE;
        stream->member_parsed = FALSE;
@@ -819,3 +870,158 @@ int json_istream_walk_stream(struct json_istream *stream,
                                   node_r);
        return 1;
 }
+
+/*
+ * Tree values
+ */
+
+static int json_istream_read_tree_common(struct json_istream *stream)
+{
+       const char *error;
+       int ret;
+
+       ret = json_istream_consume_value_stream(stream);
+       if (ret <= 0)
+               return ret;
+       ret = json_parse_more(stream->parser, &error);
+       if (ret < 0) {
+               json_istream_set_error(stream, error);
+               return ret;
+       }
+       if (stream->error != NULL)
+               return -1;
+       if (ret == 0 && !stream->node_parsed) {
+               return 0;
+       }
+       if (ret > 0) {
+               stream->end_of_input = TRUE;
+               if (!stream->node_parsed)
+                       return -1;
+       }
+       return 1;
+}
+
+int json_istream_read_tree(struct json_istream *stream,
+                          struct json_tree **tree_r)
+{
+       int ret;
+
+       i_assert(tree_r != NULL);
+
+       if (stream->closed) {
+               *tree_r = NULL;
+               return -1;
+       }
+       if (stream->end_of_input) {
+               *tree_r = NULL;
+               return -1;
+       }
+       if (stream->end_of_list) {
+               *tree_r = NULL;
+               return 1;
+       }
+
+       stream->member_parsed = FALSE;
+       if (stream->node_parsed) {
+               struct json_node root_node = stream->node;
+
+               if (stream->tree != NULL) {
+                       *tree_r = stream->tree;
+                       return 1;
+               }
+
+               i_assert(stream->node.type != JSON_TYPE_NONE);
+               i_assert(!json_node_is_end(&stream->node));
+
+               /* start tree with parsed node */
+               root_node.name = NULL;
+               stream->tree = json_tree_create();
+               stream->tree_node = json_tree_node_add(
+                       json_tree_get_root(stream->tree), &root_node);
+
+               stream->node_parsed = FALSE;
+
+               if (json_node_is_singular(&root_node)) {
+                       /* return tree with non-list item immediately */
+                       *tree_r = stream->tree;
+                       stream->tree = NULL;
+                       return 1;
+               }
+
+               stream->tree_node_level = stream->read_node_level;
+
+       } else if (stream->tree == NULL) {
+               /* start blank tree */
+               stream->tree = json_tree_create();
+               stream->tree_node = json_tree_get_root(stream->tree);
+               stream->tree_node_level = stream->read_node_level;
+       }
+
+       ret = json_istream_read_tree_common(stream);
+       if (ret <= 0) {
+               *tree_r = NULL;
+               return ret;
+       }
+
+       if (stream->end_of_list) {
+               *tree_r = NULL;
+               return 1;
+       }
+
+       *tree_r = stream->tree;
+       stream->tree = NULL;
+       json_istream_skip(stream);
+       return 1;
+}
+
+int json_istream_read_into_tree_node(struct json_istream *stream,
+                                    struct json_tree_node *tree_node)
+{
+       int ret;
+
+       if (stream->tree != NULL) {
+               if (stream->node_parsed)
+                       return 1;
+       } else {
+               if (!stream->node_parsed) {
+                       ret = json_istream_read(stream, NULL);
+                       if (ret <= 0 )
+                               return ret;
+               }
+
+               struct json_node new_node = stream->node;
+
+               i_assert(new_node.type != JSON_TYPE_NONE);
+               i_assert(!json_node_is_end(&new_node));
+
+               /* start tree branch with parsed node */
+               stream->tree_node = json_tree_node_add(tree_node, &new_node);
+
+               stream->node_parsed = FALSE;
+
+               if (json_node_is_singular(&new_node)) {
+                       stream->tree_node = NULL;
+                       json_istream_skip(stream);
+                       return 1;
+               }
+
+               stream->tree = json_tree_node_get_tree(tree_node);
+               json_tree_ref(stream->tree);
+
+               stream->tree_node_level = stream->read_node_level;
+       }
+
+       ret = json_istream_read_tree_common(stream);
+       if (ret <= 0)
+               return ret;
+
+       json_istream_skip(stream);
+       return 1;
+}
+
+int json_istream_read_into_tree(struct json_istream *stream,
+                               struct json_tree *tree)
+{
+       return json_istream_read_into_tree_node(
+               stream, json_tree_get_root(tree));
+}
index 0bbfd3fded137f25c27d22d85d9af5f662aa8f55..cf260f6818eaedb49ae9f3f71535a1a8e6f7c7aa 100644 (file)
@@ -1,6 +1,7 @@
 #ifndef JSON_ISTREAM_H
 #define JSON_ISTREAM_H
 
+#include "json-tree.new.h"
 #include "json-parser.new.h"
 
 // FIXME: don't bother recording values if we're only validating/skipping
@@ -172,4 +173,25 @@ int json_istream_walk_stream(struct json_istream *stream,
                             const char *temp_path_prefix,
                             struct json_node *node_r);
 
+/* Read a full JSON tree starting at the current position. If a node was
+   already read using json_istream_read(), is used as the tree root. Returns
+   1 on success, 0 if more data is needed from the input stream, or -1 upon
+   error or EOF. The last node in an array/object reads as *tree_r == NULL. The
+   next json_istream_read*() will read the node right after the tree, so
+   calling json_istream_skip() afterwards is not needed.
+   */
+int json_istream_read_tree(struct json_istream *stream,
+                          struct json_tree **tree_r);
+
+/* Same as json_istream_read_tree(), but read current node from stream and all
+   its children into the provided existing tree node as a new child. */
+int json_istream_read_into_tree_node(struct json_istream *stream,
+                                    struct json_tree_node *tree_node);
+/* Same as json_istream_read_tree(), but read current node from stream and all
+   children into the provided existing tree at the root. If there is no root,
+   the read node becomes the tree root. Otherwise, it is added as a new child of
+   the tree root. */
+int json_istream_read_into_tree(struct json_istream *stream,
+                               struct json_tree *tree);
+
 #endif
index f80d9411a3f541390be261e58bfa85230046d554..ba32014b70566ea27e74f67ffea7a9a91646a1f6 100644 (file)
@@ -2353,6 +2353,503 @@ static void test_json_istream_finish(void)
        i_stream_unref(&input);
 }
 
+/*
+ * Test: read tree
+ */
+
+static void test_json_istream_read_tree(void)
+{
+       struct istream *input;
+       struct json_istream *jinput;
+       const char *text;
+       struct json_tree *jtree;
+       struct json_tree_node *jtnode;
+       struct json_node jnode;
+       unsigned int pos, text_len, state;
+       int ret = 0;
+
+       /* number */
+       text = "2234234";
+       text_len = strlen(text);
+
+       input = test_istream_create_data(text, text_len);
+       jinput = json_istream_create(input, 0, NULL, 0);
+
+       test_begin("json istream read tree - number");
+
+       pos = 0; state = 0; ret = 0;
+       while (ret >= 0 && state <= 1) {
+               if (pos <= text_len)
+                       pos++;
+               test_istream_set_size(input, pos);
+               ret = json_istream_read_tree(jinput, &jtree);
+               if (ret == 0)
+                       continue;
+               if (ret < 0)
+                       break;
+               jtnode = json_tree_get_root(jtree);
+               test_assert(json_tree_node_get_type(jtnode) ==
+                           JSON_TYPE_NUMBER);
+               test_assert(json_tree_node_get_next(jtnode) == NULL);
+               json_tree_unref(&jtree);
+               state++;
+       }
+       test_assert(state == 1);
+       test_istream_set_size(input, text_len);
+       test_json_read_success(&jinput);
+
+       test_end();
+
+       json_istream_unref(&jinput);
+       i_stream_unref(&input);
+
+       /* string */
+       text = "\"frop\"";
+       text_len = strlen(text);
+
+       input = test_istream_create_data(text, text_len);
+       jinput = json_istream_create(input, 0, NULL, 0);
+
+       test_begin("json istream read tree - string");
+
+       pos = 0; state = 0; ret = 0;
+       while (ret >= 0 && state <= 1) {
+               if (pos <= text_len)
+                       pos++;
+               test_istream_set_size(input, pos);
+               ret = json_istream_read_tree(jinput, &jtree);
+               if (ret == 0)
+                       continue;
+               if (ret < 0)
+                       break;
+               jtnode = json_tree_get_root(jtree);
+               test_assert(json_tree_node_get_type(jtnode) ==
+                           JSON_TYPE_STRING);
+               test_assert(json_tree_node_get_next(jtnode) == NULL);
+               json_tree_unref(&jtree);
+               state++;
+       }
+       test_assert(state == 1);
+       test_istream_set_size(input, text_len);
+       test_json_read_success(&jinput);
+
+       test_end();
+
+       json_istream_unref(&jinput);
+       i_stream_unref(&input);
+
+       /* array */
+       text = "[\"frop\"]";
+       text_len = strlen(text);
+
+       input = test_istream_create_data(text, text_len);
+       jinput = json_istream_create(input, 0, NULL, 0);
+
+       test_begin("json istream read tree - array");
+
+       pos = 0; state = 0; ret = 0;
+       while (ret >= 0 && state <= 1) {
+               if (pos <= text_len)
+                       pos++;
+               test_istream_set_size(input, pos);
+               ret = json_istream_read_tree(jinput, &jtree);
+               if (ret == 0)
+                       continue;
+               if (ret < 0)
+                       break;
+               jtnode = json_tree_get_root(jtree);
+               test_assert(json_tree_node_get_type(jtnode) ==
+                           JSON_TYPE_ARRAY);
+               jtnode = json_tree_node_get_child(jtnode);
+               test_assert(json_tree_node_get_type(jtnode) ==
+                           JSON_TYPE_STRING);
+               test_assert(json_tree_node_get_next(jtnode) == NULL);
+               jtnode = json_tree_node_get_parent(jtnode);
+               test_assert(json_tree_node_get_type(jtnode) ==
+                           JSON_TYPE_ARRAY);
+               test_assert(json_tree_node_get_next(jtnode) == NULL);
+               json_tree_unref(&jtree);
+               state++;
+       }
+       test_assert(state == 1);
+       test_istream_set_size(input, text_len);
+       test_json_read_success(&jinput);
+
+       test_end();
+
+       json_istream_unref(&jinput);
+       i_stream_unref(&input);
+
+       /* array */
+       text = "[\"frop\", {\"a\":1234, \"b\":[1, 2, 3, 4], "
+               "\"c\":1234}, \"frop\"]";
+       text_len = strlen(text);
+
+       input = test_istream_create_data(text, text_len);
+       jinput = json_istream_create(input, 0, NULL, 0);
+
+       test_begin("json istream read tree - sequence");
+
+       pos = 0; state = 0; ret = 0;
+       while (ret >= 0 && state <= 4) {
+               if (pos <= text_len)
+                       pos++;
+               test_istream_set_size(input, pos);
+               switch (state) {
+               case 0:
+                       ret = json_istream_descend(jinput, &jnode);
+                       if (ret == 0)
+                               continue;
+                       if (ret < 0)
+                               break;
+                       test_assert(json_node_is_array(&jnode));
+                       state++;
+                       break;
+               case 1:
+                       ret = json_istream_read_tree(jinput, &jtree);
+                       if (ret == 0)
+                               continue;
+                       if (ret < 0)
+                               break;
+                       jtnode = json_tree_get_root(jtree);
+                       test_assert(json_tree_node_get_type(jtnode) ==
+                                   JSON_TYPE_STRING);
+                       test_assert(json_tree_node_get_next(jtnode) == NULL);
+                       json_tree_unref(&jtree);
+                       state++;
+                       break;
+               case 2:
+                       ret = json_istream_read_tree(jinput, &jtree);
+                       if (ret == 0)
+                               continue;
+                       if (ret < 0)
+                               break;
+                       jtnode = json_tree_get_root(jtree);
+                       test_assert(json_tree_node_get_type(jtnode) ==
+                                   JSON_TYPE_OBJECT);
+                       jtnode = json_tree_node_get_child(jtnode);
+                       test_assert(json_tree_node_get_type(jtnode) ==
+                                   JSON_TYPE_NUMBER);
+                       jtnode = json_tree_node_get_next(jtnode);
+                       test_assert(json_tree_node_get_type(jtnode) ==
+                                   JSON_TYPE_ARRAY);
+                       jtnode = json_tree_node_get_child(jtnode);
+                       test_assert(json_tree_node_get_type(jtnode) ==
+                                   JSON_TYPE_NUMBER);
+                       jtnode = json_tree_node_get_next(jtnode);
+                       test_assert(json_tree_node_get_type(jtnode) ==
+                                   JSON_TYPE_NUMBER);
+                       jtnode = json_tree_node_get_next(jtnode);
+                       test_assert(json_tree_node_get_type(jtnode) ==
+                                   JSON_TYPE_NUMBER);
+                       jtnode = json_tree_node_get_next(jtnode);
+                       test_assert(json_tree_node_get_type(jtnode) ==
+                                   JSON_TYPE_NUMBER);
+                       test_assert(json_tree_node_get_next(jtnode) == NULL);
+                       jtnode = json_tree_node_get_parent(jtnode);
+                       test_assert(json_tree_node_get_type(jtnode) ==
+                                   JSON_TYPE_ARRAY);
+                       jtnode = json_tree_node_get_next(jtnode);
+                       test_assert(json_tree_node_get_type(jtnode) ==
+                                   JSON_TYPE_NUMBER);
+                       test_assert(json_tree_node_get_next(jtnode) == NULL);
+                       jtnode = json_tree_node_get_parent(jtnode);
+                       test_assert(json_tree_node_get_type(jtnode) ==
+                                   JSON_TYPE_OBJECT);
+                       test_assert(json_tree_node_get_next(jtnode) == NULL);
+                       json_tree_unref(&jtree);
+                       state++;
+                       break;
+               case 3:
+                       ret = json_istream_read_tree(jinput, &jtree);
+                       if (ret == 0)
+                               continue;
+                       if (ret < 0)
+                               break;
+                       jtnode = json_tree_get_root(jtree);
+                       test_assert(json_tree_node_get_type(jtnode) ==
+                                   JSON_TYPE_STRING);
+                       test_assert(json_tree_node_get_next(jtnode) == NULL);
+                       json_tree_unref(&jtree);
+                       json_istream_ascend(jinput);
+                       state++;
+                       break;
+               case 4:
+                       ret = json_istream_read(jinput, &jnode);
+                       if (ret == 0)
+                               continue;
+                       if (ret < 0)
+                               break;
+                       state++;
+                       break;
+               }
+       }
+       test_assert(state == 4);
+       test_istream_set_size(input, text_len);
+       test_json_read_success(&jinput);
+
+       test_end();
+
+       json_istream_unref(&jinput);
+       i_stream_unref(&input);
+}
+
+/*
+ * Test: read into tree
+ */
+
+static void test_json_istream_read_into_tree(void)
+{
+       struct istream *input;
+       struct json_istream *jinput;
+       const char *text;
+       struct json_tree *jtree;
+       struct json_node jnode;
+       struct json_tree_node *jtnode;
+       unsigned int pos, text_len, state;
+       int ret = 0;
+
+       /* number */
+       jtree = json_tree_create();
+       text = "2234234";
+       text_len = strlen(text);
+
+       input = test_istream_create_data(text, text_len);
+       jinput = json_istream_create(input, 0, NULL, 0);
+
+       test_begin("json istream read into tree - number");
+
+       pos = 0; state = 0; ret = 0;
+       while (ret >= 0 && state <= 1) {
+               if (pos <= text_len)
+                       pos++;
+               test_istream_set_size(input, pos);
+               ret = json_istream_read_into_tree(jinput, jtree);
+               if (ret == 0)
+                       continue;
+               if (ret < 0)
+                       break;
+               jtnode = json_tree_get_root(jtree);
+               test_assert(json_tree_node_get_type(jtnode) ==
+                           JSON_TYPE_NUMBER);
+               test_assert(json_tree_node_get_next(jtnode) == NULL);
+               state++;
+       }
+       test_assert(state == 1);
+       test_istream_set_size(input, text_len);
+       test_json_read_success(&jinput);
+
+       test_end();
+
+       json_istream_unref(&jinput);
+       i_stream_unref(&input);
+       json_tree_unref(&jtree);
+
+       /* string */
+       jtree = json_tree_create();
+       text = "\"frop\"";
+       text_len = strlen(text);
+
+       input = test_istream_create_data(text, text_len);
+       jinput = json_istream_create(input, 0, NULL, 0);
+
+       test_begin("json istream read into tree - string");
+
+       pos = 0; state = 0; ret = 0;
+       while (ret >= 0 && state <= 1) {
+               if (pos <= text_len)
+                       pos++;
+               test_istream_set_size(input, pos);
+               ret = json_istream_read_into_tree(jinput, jtree);
+               if (ret == 0)
+                       continue;
+               if (ret < 0)
+                       break;
+               jtnode = json_tree_get_root(jtree);
+               test_assert(json_tree_node_get_type(jtnode) ==
+                           JSON_TYPE_STRING);
+               test_assert(json_tree_node_get_next(jtnode) == NULL);
+               state++;
+       }
+       test_assert(state == 1);
+       test_istream_set_size(input, text_len);
+       test_json_read_success(&jinput);
+
+       test_end();
+
+       json_istream_unref(&jinput);
+       i_stream_unref(&input);
+       json_tree_unref(&jtree);
+
+       /* array */
+       jtree = json_tree_create();
+       text = "[\"frop\"]";
+       text_len = strlen(text);
+
+       input = test_istream_create_data(text, text_len);
+       jinput = json_istream_create(input, 0, NULL, 0);
+
+       test_begin("json istream read into tree - array");
+
+       pos = 0; state = 0; ret = 0;
+       while (ret >= 0 && state <= 1) {
+               if (pos <= text_len)
+                       pos++;
+               test_istream_set_size(input, pos);
+               ret = json_istream_read_into_tree(jinput, jtree);
+               if (ret == 0)
+                       continue;
+               if (ret < 0)
+                       break;
+               jtnode = json_tree_get_root(jtree);
+               test_assert(json_tree_node_get_type(jtnode) ==
+                           JSON_TYPE_ARRAY);
+               jtnode = json_tree_node_get_child(jtnode);
+               test_assert(json_tree_node_get_type(jtnode) ==
+                           JSON_TYPE_STRING);
+               test_assert(json_tree_node_get_next(jtnode) == NULL);
+               jtnode = json_tree_node_get_parent(jtnode);
+               test_assert(json_tree_node_get_type(jtnode) ==
+                           JSON_TYPE_ARRAY);
+               test_assert(json_tree_node_get_next(jtnode) == NULL);
+               state++;
+       }
+       test_assert(state == 1);
+       test_istream_set_size(input, text_len);
+       test_json_read_success(&jinput);
+
+       test_end();
+
+       json_istream_unref(&jinput);
+       i_stream_unref(&input);
+       json_tree_unref(&jtree);
+
+       /* sequence */
+       jtree = json_tree_create();
+       (void)json_tree_node_add_array(json_tree_get_root(jtree), NULL);
+       text = "[\"frop\", {\"a\":1234, \"b\":[1, 2, 3, 4], "
+               "\"c\":1234}, \"frop\"]";
+       text_len = strlen(text);
+
+       input = test_istream_create_data(text, text_len);
+       jinput = json_istream_create(input, 0, NULL, 0);
+
+       test_begin("json istream read into tree - sequence");
+
+       pos = 0; state = 0; ret = 0;
+       while (ret >= 0 && state <= 4) {
+               if (pos <= text_len)
+                       pos++;
+               test_istream_set_size(input, pos);
+               switch (state) {
+               case 0:
+                       ret = json_istream_descend(jinput, &jnode);
+                       if (ret == 0)
+                               continue;
+                       if (ret < 0)
+                               break;
+                       test_assert(json_node_is_array(&jnode));
+                       state++;
+                       break;
+               case 1:
+                       ret = json_istream_read_into_tree(jinput, jtree);
+                       if (ret == 0)
+                               continue;
+                       if (ret < 0)
+                               break;
+                       state++;
+                       break;
+               case 2:
+                       ret = json_istream_read_into_tree(jinput, jtree);
+                       if (ret == 0)
+                               continue;
+                       if (ret < 0)
+                               break;
+                       state++;
+                       break;
+               case 3:
+                       ret = json_istream_read_into_tree(jinput, jtree);
+                       if (ret == 0)
+                               continue;
+                       if (ret < 0)
+                               break;
+                       state++;
+                       break;
+               case 4:
+                       ret = json_istream_read(jinput, &jnode);
+                       if (ret == 0)
+                               continue;
+                       if (ret < 0)
+                               break;
+
+                       jtnode = json_tree_get_root(jtree);
+                       test_assert(json_tree_node_get_type(jtnode) ==
+                                   JSON_TYPE_ARRAY);
+                       jtnode = json_tree_node_get_child(jtnode);
+                       test_assert(json_tree_node_get_type(jtnode) ==
+                                   JSON_TYPE_STRING);
+                       jtnode = json_tree_node_get_next(jtnode);
+                       test_assert(json_tree_node_get_type(jtnode) ==
+                                   JSON_TYPE_OBJECT);
+                       jtnode = json_tree_node_get_child(jtnode);
+                       test_assert(json_tree_node_get_type(jtnode) ==
+                                   JSON_TYPE_NUMBER);
+                       test_assert_strcmp(json_tree_node_get_name(jtnode),
+                                          "a");
+                       jtnode = json_tree_node_get_next(jtnode);
+                       test_assert(json_tree_node_get_type(jtnode) ==
+                                   JSON_TYPE_ARRAY);
+                       test_assert_strcmp(json_tree_node_get_name(jtnode),
+                                          "b");
+                       jtnode = json_tree_node_get_child(jtnode);
+                       test_assert(json_tree_node_get_type(jtnode) ==
+                                   JSON_TYPE_NUMBER);
+                       jtnode = json_tree_node_get_next(jtnode);
+                       test_assert(json_tree_node_get_type(jtnode) ==
+                                   JSON_TYPE_NUMBER);
+                       jtnode = json_tree_node_get_next(jtnode);
+                       test_assert(json_tree_node_get_type(jtnode) ==
+                                   JSON_TYPE_NUMBER);
+                       jtnode = json_tree_node_get_next(jtnode);
+                       test_assert(json_tree_node_get_type(jtnode) ==
+                                   JSON_TYPE_NUMBER);
+                       test_assert(json_tree_node_get_next(jtnode) == NULL);
+                       jtnode = json_tree_node_get_parent(jtnode);
+                       test_assert(json_tree_node_get_type(jtnode) ==
+                                   JSON_TYPE_ARRAY);
+                       jtnode = json_tree_node_get_next(jtnode);
+                       test_assert(json_tree_node_get_type(jtnode) ==
+                                   JSON_TYPE_NUMBER);
+                       test_assert_strcmp(json_tree_node_get_name(jtnode),
+                                          "c");
+                       test_assert(json_tree_node_get_next(jtnode) == NULL);
+                       jtnode = json_tree_node_get_parent(jtnode);
+                       test_assert(json_tree_node_get_type(jtnode) ==
+                                   JSON_TYPE_OBJECT);
+                       jtnode = json_tree_node_get_next(jtnode);
+                       test_assert(json_tree_node_get_type(jtnode) ==
+                                   JSON_TYPE_STRING);
+                       test_assert(json_tree_node_get_next(jtnode) == NULL);
+                       jtnode = json_tree_node_get_parent(jtnode);
+                       test_assert(json_tree_node_get_type(jtnode) ==
+                                   JSON_TYPE_ARRAY);
+                       test_assert(json_tree_node_get_next(jtnode) == NULL);
+
+                       json_istream_ascend(jinput);
+                       state++;
+                       break;
+               }
+       }
+       test_assert(state == 5);
+       test_istream_set_size(input, text_len);
+       test_json_read_success(&jinput);
+
+       test_end();
+
+       i_stream_unref(&input);
+       json_tree_unref(&jtree);
+}
+
 /*
  * Test: read stream
  */
@@ -3130,6 +3627,7 @@ static void test_json_istream_error(void)
        struct json_istream *jinput;
        const char *text, *error;
        struct json_node jnode;
+       struct json_tree *jtree;
        unsigned int text_len;
        int ret = 0;
 
@@ -3193,6 +3691,24 @@ static void test_json_istream_error(void)
        json_istream_unref(&jinput);
        i_stream_unref(&input);
 
+       /* tree parse error */
+       text = "{\"a\":[0],\"b\":[1],\"c\":[2],\"d\":[\"unclosed array\"}";
+       text_len = strlen(text);
+
+       input = i_stream_create_from_data(text, text_len);
+       jinput = json_istream_create(input, 0, NULL, 0);
+
+       test_begin("json istream error - tree parse error");
+
+       ret = json_istream_read_tree(jinput, &jtree);
+       error = json_istream_get_error(jinput);
+       test_out_reason("read failure", (ret < 0 && error != NULL), error);
+
+       test_end();
+
+       json_istream_unref(&jinput);
+       i_stream_unref(&input);
+
        /* spurious data at end of input */
        text = "[\"data\"][\"junk\"]";
        text_len = strlen(text);
@@ -3347,6 +3863,8 @@ int main(int argc, char *argv[])
                test_json_istream_read_buffer,
                test_json_istream_read_trickle,
                test_json_istream_finish,
+               test_json_istream_read_tree,
+               test_json_istream_read_into_tree,
                test_json_istream_read_stream,
                test_json_istream_tokens_buffer,
                test_json_istream_tokens_trickle,