dovecot-2.2: JSON parser: Added support for arrays.

dovecot at dovecot.org dovecot at dovecot.org
Thu Nov 29 10:29:31 EET 2012


details:   http://hg.dovecot.org/dovecot-2.2/rev/f456fffeec5c
changeset: 15439:f456fffeec5c
user:      Timo Sirainen <tss at iki.fi>
date:      Thu Nov 29 10:29:19 2012 +0200
description:
JSON parser: Added support for arrays.
The parser should be complete now.

diffstat:

 src/lib/json-parser.c      |  143 +++++++++++++++++++++++++++++++++-----------
 src/lib/json-parser.h      |    6 +-
 src/lib/test-json-parser.c |   24 ++++++-
 3 files changed, 133 insertions(+), 40 deletions(-)

diffs (truncated from 344 to 300 lines):

diff -r 4f7e0be9cc35 -r f456fffeec5c src/lib/json-parser.c
--- a/src/lib/json-parser.c	Thu Nov 29 09:38:34 2012 +0200
+++ b/src/lib/json-parser.c	Thu Nov 29 10:29:19 2012 +0200
@@ -1,6 +1,7 @@
 /* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
 
 #include "lib.h"
+#include "array.h"
 #include "str.h"
 #include "istream.h"
 #include "hex-dec.h"
@@ -14,8 +15,12 @@
 	JSON_STATE_OBJECT_KEY,
 	JSON_STATE_OBJECT_COLON,
 	JSON_STATE_OBJECT_VALUE,
-	JSON_STATE_OBJECT_VALUE_NEXT,
-	JSON_STATE_SKIP_STRING,
+	JSON_STATE_OBJECT_SKIP_STRING,
+	JSON_STATE_OBJECT_NEXT,
+	JSON_STATE_ARRAY_OPEN,
+	JSON_STATE_ARRAY_VALUE,
+	JSON_STATE_ARRAY_SKIP_STRING,
+	JSON_STATE_ARRAY_NEXT,
 	JSON_STATE_DONE
 };
 
@@ -29,7 +34,7 @@
 	struct istream *strinput;
 
 	enum json_state state;
-	unsigned int nested_object_count;
+	ARRAY(enum json_state) nesting;
 	unsigned int nested_skip_count;
 	bool skipping;
 };
@@ -93,6 +98,7 @@
 	parser = i_new(struct json_parser, 1);
 	parser->input = input;
 	parser->value = str_new(default_pool, 128);
+	i_array_init(&parser->nesting, 8);
 	i_stream_ref(input);
 	return parser;
 }
@@ -111,7 +117,6 @@
 					   i_stream_get_name(parser->input));
 	} else if (parser->data == parser->end &&
 		   !i_stream_have_bytes_left(parser->input) &&
-		   parser->state != JSON_STATE_ROOT &&
 		   parser->state != JSON_STATE_DONE) {
 		*error_r = "Missing '}'";
 	} else {
@@ -119,6 +124,7 @@
 	}
 	
 	i_stream_unref(&parser->input);
+	array_free(&parser->nesting);
 	str_free(&parser->value);
 	i_free(parser);
 	return *error_r != NULL ? -1 : 0;
@@ -318,25 +324,55 @@
 	return 1;
 }
 
-static int
-json_parse_object_close(struct json_parser *parser, enum json_type *type_r)
+static int json_parse_denest(struct json_parser *parser)
 {
+	const enum json_state *nested_states;
+	unsigned count;
+
 	parser->data++;
 	json_parser_update_input_pos(parser);
 
-	if (parser->nested_object_count > 0) {
-		/* closing a nested object */
-		parser->nested_object_count--;
-		parser->state = JSON_STATE_OBJECT_VALUE_NEXT;
-		if (parser->nested_skip_count > 0) {
-			parser->nested_skip_count--;
-			return 0;
-		}
-		*type_r = JSON_TYPE_OBJECT_END;
-		return 1;
+	nested_states = array_get(&parser->nesting, &count);
+	if (count == 0) {
+		/* closing root */
+		parser->state = JSON_STATE_DONE;
+		return 0;
 	}
-	parser->state = JSON_STATE_DONE;
-	return 0;
+
+	/* closing a nested object */
+	if (count == 1) {
+		/* we're back to root */
+		parser->state = JSON_STATE_OBJECT_NEXT;
+	} else {
+		/* back to previous nested object */
+		parser->state = nested_states[count-2] == JSON_STATE_OBJECT_OPEN ?
+			JSON_STATE_OBJECT_NEXT : JSON_STATE_ARRAY_NEXT;
+	}
+	array_delete(&parser->nesting, count-1, 1);
+
+	if (parser->nested_skip_count > 0) {
+		parser->nested_skip_count--;
+		return 0;
+	}
+	return 1;
+}
+
+static int
+json_parse_close_object(struct json_parser *parser, enum json_type *type_r)
+{
+	if (json_parse_denest(parser) == 0)
+		return 0;
+	*type_r = JSON_TYPE_OBJECT_END;
+	return 1;
+}
+
+static int
+json_parse_close_array(struct json_parser *parser, enum json_type *type_r)
+{
+	if (json_parse_denest(parser) == 0)
+		return 0;
+	*type_r = JSON_TYPE_ARRAY_END;
+	return 1;
 }
 
 static int
@@ -360,13 +396,11 @@
 		json_parser_update_input_pos(parser);
 		return 0;
 	case JSON_STATE_OBJECT_VALUE:
-		if (*parser->data == '[') {
-			parser->error = "Arrays not supported";
-			return -1;
-		} else if (*parser->data == '{') {
+	case JSON_STATE_ARRAY_VALUE:
+		if (*parser->data == '{') {
 			parser->data++;
 			parser->state = JSON_STATE_OBJECT_OPEN;
-			parser->nested_object_count++;
+			array_append(&parser->nesting, &parser->state, 1);
 			json_parser_update_input_pos(parser);
 
 			if (parser->skipping) {
@@ -375,7 +409,20 @@
 			}
 			*type_r = JSON_TYPE_OBJECT;
 			return 1;
+		} else if (*parser->data == '[') {
+			parser->data++;
+			parser->state = JSON_STATE_ARRAY_OPEN;
+			array_append(&parser->nesting, &parser->state, 1);
+			json_parser_update_input_pos(parser);
+
+			if (parser->skipping) {
+				parser->nested_skip_count++;
+				return 0;
+			}
+			*type_r = JSON_TYPE_ARRAY;
+			return 1;
 		}
+
 		if ((ret = json_parse_string(parser, TRUE, value_r)) >= 0) {
 			*type_r = JSON_TYPE_STRING;
 		} else if ((ret = json_parse_number(parser, value_r)) >= 0) {
@@ -398,18 +445,19 @@
 			if (parser->skipping && *type_r == JSON_TYPE_STRING) {
 				/* a large string that we want to skip over. */
 				json_parser_update_input_pos(parser);
-				parser->state = JSON_STATE_SKIP_STRING;
+				parser->state = parser->state == JSON_STATE_OBJECT_VALUE ?
+					JSON_STATE_OBJECT_SKIP_STRING :
+					JSON_STATE_ARRAY_SKIP_STRING;
 				return 0;
 			}
 			return -1;
 		}
-		parser->state = parser->state == JSON_STATE_ROOT ?
-			JSON_STATE_DONE :
-			JSON_STATE_OBJECT_VALUE_NEXT;
+		parser->state = parser->state == JSON_STATE_OBJECT_VALUE ?
+			JSON_STATE_OBJECT_NEXT : JSON_STATE_ARRAY_NEXT;
 		break;
 	case JSON_STATE_OBJECT_OPEN:
 		if (*parser->data == '}')
-			return json_parse_object_close(parser, type_r);
+			return json_parse_close_object(parser, type_r);
 		parser->state = JSON_STATE_OBJECT_KEY;
 		/* fall through */
 	case JSON_STATE_OBJECT_KEY:
@@ -429,13 +477,13 @@
 		parser->state = JSON_STATE_OBJECT_VALUE;
 		json_parser_update_input_pos(parser);
 		return 0;
-	case JSON_STATE_OBJECT_VALUE_NEXT:
+	case JSON_STATE_OBJECT_NEXT:
 		if (parser->skipping && parser->nested_skip_count == 0) {
 			/* we skipped over the previous value */
 			parser->skipping = FALSE;
 		}
 		if (*parser->data == '}')
-			return json_parse_object_close(parser, type_r);
+			return json_parse_close_object(parser, type_r);
 		if (*parser->data != ',') {
 			parser->error = "Expected ',' or '}' after object value";
 			return -1;
@@ -444,10 +492,32 @@
 		parser->data++;
 		json_parser_update_input_pos(parser);
 		return 0;
-	case JSON_STATE_SKIP_STRING:
+	case JSON_STATE_ARRAY_OPEN:
+		if (*parser->data == ']')
+			return json_parse_close_array(parser, type_r);
+		parser->state = JSON_STATE_ARRAY_VALUE;
+		return 0;
+	case JSON_STATE_ARRAY_NEXT:
+		if (parser->skipping && parser->nested_skip_count == 0) {
+			/* we skipped over the previous value */
+			parser->skipping = FALSE;
+		}
+		if (*parser->data == ']')
+			return json_parse_close_array(parser, type_r);
+		if (*parser->data != ',') {
+			parser->error = "Expected ',' or '}' after array value";
+			return -1;
+		}
+		parser->state = JSON_STATE_ARRAY_VALUE;
+		parser->data++;
+		json_parser_update_input_pos(parser);
+		return 0;
+	case JSON_STATE_OBJECT_SKIP_STRING:
+	case JSON_STATE_ARRAY_SKIP_STRING:
 		if (json_skip_string(parser) <= 0)
 			return -1;
-		parser->state = JSON_STATE_OBJECT_VALUE_NEXT;
+		parser->state = parser->state == JSON_STATE_OBJECT_SKIP_STRING ?
+			JSON_STATE_OBJECT_NEXT : JSON_STATE_ARRAY_NEXT;
 		return 0;
 	case JSON_STATE_DONE:
 		parser->error = "Unexpected data at the end";
@@ -486,7 +556,8 @@
 	i_assert(!parser->skipping);
 	i_assert(parser->strinput == NULL);
 	i_assert(parser->state == JSON_STATE_OBJECT_COLON ||
-		 parser->state == JSON_STATE_OBJECT_VALUE);
+		 parser->state == JSON_STATE_OBJECT_VALUE ||
+		 parser->state == JSON_STATE_ARRAY_VALUE);
 
 	parser->skipping = TRUE;
 }
@@ -521,7 +592,8 @@
 	parser->data++;
 	json_parser_update_input_pos(parser);
 
-	parser->state = JSON_STATE_SKIP_STRING;
+	parser->state = parser->state == JSON_STATE_OBJECT_VALUE ?
+		JSON_STATE_OBJECT_SKIP_STRING : JSON_STATE_ARRAY_SKIP_STRING;
 	parser->strinput = i_stream_create_jsonstr(parser->input);
 	i_stream_set_destroy_callback(parser->strinput,
 				      json_strinput_destroyed, parser);
@@ -538,7 +610,8 @@
 	i_assert(!parser->skipping);
 	i_assert(parser->strinput == NULL);
 	i_assert(parser->state == JSON_STATE_OBJECT_COLON ||
-		 parser->state == JSON_STATE_OBJECT_VALUE);
+		 parser->state == JSON_STATE_OBJECT_VALUE ||
+		 parser->state == JSON_STATE_ARRAY_VALUE);
 
 	*input_r = NULL;
 
diff -r 4f7e0be9cc35 -r f456fffeec5c src/lib/json-parser.h
--- a/src/lib/json-parser.h	Thu Nov 29 09:38:34 2012 +0200
+++ b/src/lib/json-parser.h	Thu Nov 29 10:29:19 2012 +0200
@@ -9,6 +9,9 @@
 	/* } (not returned for the root object) */
 	JSON_TYPE_OBJECT_END,
 
+	JSON_TYPE_ARRAY,
+	JSON_TYPE_ARRAY_END,
+
 	JSON_TYPE_STRING,
 	JSON_TYPE_NUMBER,
 	JSON_TYPE_TRUE,
@@ -16,8 +19,7 @@
 	JSON_TYPE_NULL
 };
 
-/* Parse JSON tokens from the input stream. Currently arrays aren't
-   supported. */
+/* Parse JSON tokens from the input stream. */
 struct json_parser *json_parser_init(struct istream *input);
 int json_parser_deinit(struct json_parser **parser, const char **error_r);
 
diff -r 4f7e0be9cc35 -r f456fffeec5c src/lib/test-json-parser.c
--- a/src/lib/test-json-parser.c	Thu Nov 29 09:38:34 2012 +0200
+++ b/src/lib/test-json-parser.c	Thu Nov 29 10:29:19 2012 +0200
@@ -15,7 +15,7 @@


More information about the dovecot-cvs mailing list