[ovs-dev] [PATCH] Implement JSON parsing and serialization.

Ben Pfaff blp at nicira.com
Thu Sep 24 17:35:25 UTC 2009


This will be used by the upcoming Open vSwitch configuration database.
---
This is really a "pre-review", in that I am not proposing to push it to
"master" or "citrix" any time soon.  Instead, it is a building block
for the database.  Perhaps it would be appropriate to push it to a new
"ovsdb" topic branch.

 lib/automake.mk      |    4 +
 lib/dynamic-string.c |   25 ++
 lib/dynamic-string.h |    1 +
 lib/json.c           | 1043 ++++++++++++++++++++++++++++++++++++++++++++++++++
 lib/json.h           |   70 ++++
 lib/shash.c          |    6 +
 lib/shash.h          |    1 +
 lib/unicode.c        |   38 ++
 lib/unicode.h        |   53 +++
 lib/util.c           |   55 +++
 lib/util.h           |    4 +
 tests/.gitignore     |    1 +
 tests/automake.mk    |    5 +
 tests/json.at        |  211 ++++++++++
 tests/test-json.c    |   45 +++
 tests/testsuite.at   |    1 +
 16 files changed, 1563 insertions(+), 0 deletions(-)
 create mode 100644 lib/json.c
 create mode 100644 lib/json.h
 create mode 100644 lib/unicode.c
 create mode 100644 lib/unicode.h
 create mode 100644 tests/json.at
 create mode 100644 tests/test-json.c

diff --git a/lib/automake.mk b/lib/automake.mk
index 9ba513a..2f7b27d 100644
--- a/lib/automake.mk
+++ b/lib/automake.mk
@@ -49,6 +49,8 @@ lib_libopenvswitch_a_SOURCES = \
 	lib/hash.h \
 	lib/hmap.c \
 	lib/hmap.h \
+	lib/json.c \
+	lib/json.h \
 	lib/leak-checker.c \
 	lib/leak-checker.h \
 	lib/learning-switch.c \
@@ -105,6 +107,8 @@ lib_libopenvswitch_a_SOURCES = \
 	lib/type-props.h \
 	lib/unixctl.c \
 	lib/unixctl.h \
+	lib/unicode.c \
+	lib/unicode.h \
 	lib/util.c \
 	lib/util.h \
 	lib/valgrind.h \
diff --git a/lib/dynamic-string.c b/lib/dynamic-string.c
index 9684ffa..531e53b 100644
--- a/lib/dynamic-string.c
+++ b/lib/dynamic-string.c
@@ -71,6 +71,31 @@ ds_put_char(struct ds *ds, char c)
     *ds_put_uninit(ds, 1) = c;
 }
 
+/* Appends unicode code point 'uc' to 'ds' in UTF-8 encoding. */
+void
+ds_put_utf8(struct ds *ds, int uc)
+{
+    if (uc <= 0x7f) {
+        ds_put_char(ds, uc);
+    } else if (uc <= 0x7ff) {
+        ds_put_char(ds, 0xc0 | (uc >> 6));
+        ds_put_char(ds, 0x80 | (uc & 0x3f));
+    } else if (uc <= 0xffff) {
+        ds_put_char(ds, 0xe0 | (uc >> 12));
+        ds_put_char(ds, 0x80 | ((uc >> 6) & 0x3f));
+        ds_put_char(ds, 0x80 | (uc & 0x3f));
+    } else if (uc <= 0x10ffff) {
+        ds_put_char(ds, 0xf0 | (uc >> 18));
+        ds_put_char(ds, 0x80 | ((uc >> 12) & 0x3f));
+        ds_put_char(ds, 0x80 | ((uc >> 6) & 0x3f));
+        ds_put_char(ds, 0x80 | (uc & 0x3f));
+    } else {
+        /* Invalid code point.  Insert the Unicode general substitute
+         * REPLACEMENT CHARACTER. */
+        ds_put_utf8(ds, 0xfffd);
+    }
+}
+
 void
 ds_put_char_multiple(struct ds *ds, char c, size_t n)
 {
diff --git a/lib/dynamic-string.h b/lib/dynamic-string.h
index a44e0b3..01b93c4 100644
--- a/lib/dynamic-string.h
+++ b/lib/dynamic-string.h
@@ -40,6 +40,7 @@ void ds_truncate(struct ds *, size_t new_length);
 void ds_reserve(struct ds *, size_t min_length);
 char *ds_put_uninit(struct ds *, size_t n);
 void ds_put_char(struct ds *, char);
+void ds_put_utf8(struct ds *, int uc);
 void ds_put_char_multiple(struct ds *, char, size_t n);
 void ds_put_buffer(struct ds *, const char *, size_t n);
 void ds_put_cstr(struct ds *, const char *);
diff --git a/lib/json.c b/lib/json.c
new file mode 100644
index 0000000..1f331f3
--- /dev/null
+++ b/lib/json.c
@@ -0,0 +1,1043 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "json.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <float.h>
+#include <limits.h>
+#include <string.h>
+
+#include "dynamic-string.h"
+#include "shash.h"
+#include "unicode.h"
+#include "util.h"
+
+/* The type of a JSON token. */
+enum json_token_type {
+    T_EOF = 0,
+    T_BEGIN_ARRAY = '[',
+    T_END_ARRAY = ']',
+    T_BEGIN_OBJECT = '{',
+    T_END_OBJECT = '}',
+    T_NAME_SEPARATOR = ':',
+    T_VALUE_SEPARATOR = ',',
+    T_FALSE = UCHAR_MAX + 1,
+    T_NULL,
+    T_TRUE,
+    T_INTEGER,
+    T_REAL,
+    T_STRING
+};
+
+/* A JSON token.
+ *
+ * RFC 4627 doesn't define a lexical structure for JSON but I believe this to
+ * be compliant with the standard.
+ */
+struct json_token {
+    enum json_token_type type;
+    union {
+        double real;
+        long long int integer;
+        char *string;
+    } u;
+};
+
+/* A JSON parser. */
+struct json_parser {
+    /* Lexical analysis. */
+    struct json_token token;    /* Token currently being parsed. */
+    const char *input;          /* Beginning of current input chunk. */
+    const char *position;       /* First unconsumed byte of input chunk. */
+    const char *end;            /* Just past last byte of input chunk. */
+    int (*fill)(void *aux, const char **inputp); /* Call to get more input. */
+    void *fill_aux;             /* Argument to fill function. */
+
+    /* Error handling. */
+    char *error;                /* Error message, if any, null if none yet. */
+#define JSON_MAX_DEPTH 1000
+    int depth;                  /* Current depth, to avoid stack exhaustion. */
+#define JSON_MAX_SIZE 1000000
+    size_t size;                /* Number of bytes lexed, to avoid heap
+                                 * exhaustion. */
+};
+
+static struct json *json_create(enum json_type type);
+
+static void json_error(struct json_parser *p, const char *format, ...)
+    PRINTF_FORMAT(2, 3);
+
+/* Functions for manipulating struct json. */
+
+static void json_destroy_object(struct shash *object);
+static void json_destroy_array(struct json_array *array);
+
+/* Frees 'json' and everything it points to, recursively. */
+void
+json_destroy(struct json *json)
+{
+    if (json) {
+        switch (json->type) {
+        case JSON_OBJECT:
+            json_destroy_object(json->u.object);
+            break;
+
+        case JSON_ARRAY:
+            json_destroy_array(&json->u.array);
+            break;
+
+        case JSON_STRING:
+            free(json->u.string);
+            break;
+
+        case JSON_NULL:
+        case JSON_FALSE:
+        case JSON_TRUE:
+        case JSON_INTEGER:
+        case JSON_REAL:
+            break;
+        }
+        free(json);
+    }
+}
+
+static void
+json_destroy_object(struct shash *object)
+{
+    struct shash_node *node, *next;
+
+    SHASH_FOR_EACH_SAFE (node, next, object) {
+        struct json *value = node->data;
+
+        json_destroy(value);
+        shash_delete(object, node);
+    }
+    free(object);
+}
+
+static void
+json_destroy_array(struct json_array *array)
+{
+    size_t i;
+
+    for (i = 0; i < array->n; i++) {
+        json_destroy(array->elems[i]);
+    }
+    free(array->elems);
+}
+
+/* Tokens. */
+
+static void
+json_token_init(struct json_token *token)
+{
+    token->type = T_EOF;
+}
+
+static void
+json_token_destroy(struct json_token *token)
+{
+    if (token->type == T_STRING) {
+        free(token->u.string);
+    }
+}
+
+static char *
+json_token_steal_string(struct json_token *token)
+{
+    assert(token->type == T_STRING);
+    token->type = T_EOF;
+    return token->u.string;
+}
+
+/* Lexical analysis. */
+
+/* Fills 'p''s input buffer with new input from our data source.  Returns true
+ * if successful, false if we're out of input or an error occurs. */
+static bool
+json_fill(struct json_parser *p)
+{
+    const char *input;
+    int n;
+
+    n = p->fill(p->fill_aux, &input);
+    if (n > 0) {
+        p->input = p->position = input;
+        p->end = p->input + n;
+
+        p->size += n;
+        if (p->size < JSON_MAX_SIZE) {
+            return true;
+        } else {
+            json_error(p, "input exceeds max size %d", JSON_MAX_SIZE);
+        }
+    }
+    p->input = p->position = p->end = NULL;
+    return false;
+}
+
+/* Returns the next byte of input on 'p', or 0 at end of input. */
+static int
+json_getc(struct json_parser *p)
+{
+    if (p->position < p->end || (p->input && json_fill(p))) {
+        return *p->position++;
+    } else {
+        return 0;
+    }
+}
+
+/* Puts byte 'c' back onto the input queue.  'c' must be the previous byte
+ * read from 'p'.  json_ungetc() may not be called twice for a given 'p'
+ * without an intervening call to json_getc(p).  */
+static void
+json_ungetc(struct json_parser *p, int c)
+{
+    assert(p->position > p->input);
+    assert(p->position[-1] == c);
+    p->position--;
+}
+
+static bool
+json_lex_keyword(struct json_parser *p, const char *keyword,
+                 enum json_token_type type, struct json_token *token)
+{
+    int c, i;
+
+    /* Make sure that all of the characters in 'keyword' are there.  Skip
+     * keyword[0] since the caller already read that. */
+    for (i = 1; keyword[i]; i++) {
+        if (json_getc(p) != keyword[i]) {
+            goto error;
+        }
+    }
+
+    /* Make sure that 'keyword' is followed by a non-alphanumeric character.
+     * Strictly speaking this check is unnecessary, but it should make for
+     * better error messages. */
+    c = json_getc(p);
+    if (isalnum(c)) {
+        goto error;
+    }
+    json_ungetc(p, c);
+
+    token->type = type;
+    return true;
+
+error:
+    json_error(p, "invalid keyword");
+    return false;
+}
+
+static bool
+json_lex_digits(struct json_parser *p, struct ds *s, int *c)
+{
+    int n;
+    for (n = 0; isdigit(*c); n++) {
+        ds_put_char(s, *c);
+        *c = json_getc(p);
+    }
+    return n > 0;
+}
+
+static bool
+json_lex_number(struct json_parser *p, int c, struct json_token *token)
+{
+    struct ds s;
+    double d;
+
+    ds_init(&s);
+    ds_reserve(&s, 31);
+
+    /* Leading minus sign. */
+    if (c == '-') {
+        ds_put_char(&s, c);
+        c = json_getc(p);
+    }
+
+    /* At least one integer digit, but 0 may not be used as a leading digit for
+     * a longer number. */
+    if (c == '0') {
+        ds_put_char(&s, c);
+        c = json_getc(p);
+        if (isdigit(c)) {
+            json_error(p, "leading zeros not allowed");
+            goto error;
+
+        }
+    } else if (!json_lex_digits(p, &s, &c)) {
+        goto error;
+    }
+
+    /* Optional fraction. */
+    if (c == '.') {
+        ds_put_char(&s, c);
+        c = json_getc(p);
+        if (!json_lex_digits(p, &s, &c)) {
+            goto error;
+        }
+    }
+
+    /* Optional exponent. */
+    if (c == 'e' || c == 'E') {
+        ds_put_char(&s, c);
+        c = json_getc(p);
+        if (c == '+' || c == '-') {
+            ds_put_char(&s, c);
+            c = json_getc(p);
+        }
+        if (!json_lex_digits(p, &s, &c)) {
+            goto error;
+        }
+    }
+    json_ungetc(p, c);
+
+    /* Parse number. */
+    if (!str_to_double(ds_cstr(&s), &d)) {
+        json_error(p, "number \"%s\" outside valid range", ds_cstr(&s));
+        return false;
+    }
+    if (d >= LLONG_MIN && d <= LLONG_MAX && (long long int) d == d) {
+        token->type = T_INTEGER;
+        token->u.integer = d;
+    } else {
+        token->type = T_REAL;
+        token->u.real = d;
+    }
+
+    ds_destroy(&s);
+
+    return true;
+
+error:
+    json_error(p, "invalid numeric format");
+    ds_destroy(&s);
+    return false;
+}
+
+static int
+json_lex_4hex(struct json_parser *p)
+{
+    int value, i;
+
+    value = 0;
+    for (i = 0; i < 4; i++) {
+        int c = json_getc(p);
+        if (!isxdigit(c)) {
+            json_error(p, "malformed \\u escape");
+            return 0;
+        }
+        value = (value << 4) | hexit_value(c);
+    }
+    if (!value) {
+        json_error(p, "null bytes not supported in quoted strings");
+        return 0;
+    }
+    return value;
+}
+
+static int
+json_lex_unicode(struct json_parser *p)
+{
+    int c0, c1;
+
+    c0 = json_lex_4hex(p);
+    if (!uc_is_leading_surrogate(c0)) {
+        return c0;
+    }
+
+    if (json_getc(p) != '\\' || json_getc(p) != 'u') {
+        json_error(p, "malformed escaped surrogate pair");
+        return 0;
+    }
+
+    c1 = json_lex_4hex(p);
+    if (!uc_is_trailing_surrogate(c1)) {
+        json_error(p, "second half of escaped surrogate pair is not "
+                   "trailing surrogate");
+        return 0;
+    }
+
+    return utf16_decode_surrogate_pair(c0, c1);
+}
+
+static bool
+json_lex_string(struct json_parser *p, struct json_token *token)
+{
+    struct ds s;
+    int c;
+
+    ds_init(&s);
+    for (c = json_getc(p); c != '"'; c = json_getc(p)) {
+        switch (c) {
+        case 0:
+            json_error(p, "unexpected end of input in quoted string");
+            goto error;
+
+        case '\\':
+            c = json_getc(p);
+            switch (c) {
+            case '"': case '\\': case '/':
+                ds_put_char(&s, c);
+                break;
+
+            case 'b':
+                ds_put_char(&s, '\b');
+                break;
+
+            case 'f':
+                ds_put_char(&s, '\f');
+                break;
+
+            case 'n':
+                ds_put_char(&s, '\n');
+                break;
+
+            case 'r':
+                ds_put_char(&s, '\r');
+                break;
+
+            case 't':
+                ds_put_char(&s, '\t');
+                break;
+
+            case 'u':
+                c = json_lex_unicode(p);
+                if (!c) {
+                    goto error;
+                }
+                ds_put_utf8(&s, c);
+                break;
+
+            default:
+                json_error(p, "bad escape \\%c", c);
+                goto error;
+            }
+            break;
+
+        default:
+            if (c >= 0x20) {
+                ds_put_char(&s, c);
+            } else {
+                json_error(p, "U+%04X must be escaped in quoted string", c);
+                goto error;
+            }
+            break;
+        }
+    }
+
+    token->type = T_STRING;
+    token->u.string = ds_steal_cstr(&s);
+    return true;
+
+error:
+    ds_destroy(&s);
+    return false;
+}
+
+/* Retrieves the next token from 'p''s input stream into p->token.  Returns
+ * true if successful, false on error or at end of input. */
+static bool
+json_get_token(struct json_parser *p)
+{
+    struct json_token *token = &p->token;
+
+    json_token_destroy(token);
+    json_token_init(token);
+    for (;;) {
+        int c = json_getc(p);
+
+        switch (c) {
+        case 0:
+            token->type = T_EOF;
+            return true;
+
+        case ' ': case '\t': case '\n': case '\r':
+            break;
+
+        case 'f':
+            return json_lex_keyword(p, "false", T_FALSE, token);
+
+        case 't':
+            return json_lex_keyword(p, "true", T_TRUE, token);
+
+        case 'n':
+            return json_lex_keyword(p, "null", T_NULL, token);
+
+        case '[': case '{': case ']': case '}': case ':': case ',':
+            token->type = c;
+            return true;
+
+        case '-':
+        case '0': case '1': case '2': case '3': case '4':
+        case '5': case '6': case '7': case '8': case '9':
+            return json_lex_number(p, c, token);
+
+        case '"':
+            return json_lex_string(p, token);
+
+        default:
+            if (isprint(c)) {
+                json_error(p, "invalid character '%c'", c);
+            } else {
+                json_error(p, "invalid character U+%04x", c);
+            }
+            return false;
+        }
+    }
+}
+
+/* Parsing. */
+
+static struct json *json_parse_object(struct json_parser *);
+static struct json *json_parse_array(struct json_parser *);
+static struct json *json_parse_value(struct json_parser *);
+
+static int
+json_string_cb(void *stringp_, const char **inputp)
+{
+    const char **stringp = stringp_;
+    if (*stringp) {
+        *inputp = *stringp;
+        *stringp = NULL;
+        return strlen(*inputp);
+    } else {
+        return 0;
+    }
+}
+
+/* Parses 'string' as JSON syntax and returns a newly allocated 'struct
+ * json'.  The caller must free the returned structure with json_destroy()
+ * when it is no longer needed.
+ *
+ * 'string' must be encoded in UTF-8.
+ *
+ * If 'string' is valid JSON, then the returned 'struct json' will be either an
+ * object (JSON_OBJECT) or an array (JSON_ARRAY).
+ *
+ * If 'string' is not valid JSON, then the returned 'struct json' will be a
+ * string (JSON_STRING) that describes the particular error encountered during
+ * parsing.  (This is an acceptable means of error reporting because at its top
+ * level JSON must be either an object or an array; a bare string is not
+ * valid.) */
+struct json *
+json_from_string(const char *string)
+{
+    return json_from_cb(json_string_cb, &string);
+}
+
+struct json_file_aux {
+    FILE *stream;
+    char buf[BUFSIZ];
+};
+
+static int
+json_file_cb(void *aux_, const char **inputp)
+{
+    struct json_file_aux *aux = aux_;
+    size_t n = fread(aux->buf, 1, sizeof aux->buf, aux->stream);
+    *inputp = aux->buf;
+    return n;
+}
+
+/* Reads the file named 'file_name', parses its contents as JSON syntax, and
+ * returns a newly allocated 'struct json'.  The caller must free the returned
+ * structure with json_destroy() when it is no longer needed.
+ *
+ * The file must be encoded in UTF-8.
+ *
+ * See json_from_string() for return value semantics.
+ */
+struct json *
+json_from_file(const char *file_name)
+{
+    struct json_file_aux aux;
+    struct json *json;
+
+    aux.stream = fopen(file_name, "r");
+    if (!aux.stream) {
+        json = json_create(JSON_STRING);
+        json->u.string = xasprintf("%s: open failed: %s", file_name,
+                                   strerror(errno));
+        return json;
+    }
+
+    json = json_from_cb(json_file_cb, &aux);
+
+    /* XXX report file read errors */
+    fclose(aux.stream);
+
+    return json;
+}
+
+/* Parses data obtained in chunks through a callback as JSON and returns a
+ * newly allocated 'struct json'.  The caller must free the returned structure
+ * with json_destroy() when it is no longer needed.
+ *
+ * The data is obtained by calling 'cb', passing 'aux' as auxiliary data.  'cb'
+ * should obtain more data to be parsed as JSON, store a pointer to it in
+ * '*inputp', and return the number of bytes of data.  'cb' may indicate end of
+ * input or an error by returning 0.
+ *
+ * 'cb' is responsible for freeing its own data; json_from_cb() will not do it.
+ *
+ * See json_from_string() for return value semantics.
+ */
+struct json *
+json_from_cb(int (*cb)(void *aux, const char **inputp), void *aux)
+{
+    struct json_parser p;
+    struct json *json;
+
+    memset(&p, 0, sizeof p);
+    json_token_init(&p.token);
+    p.fill = cb;
+    p.fill_aux = aux;
+
+    json = NULL;
+    if (!json_fill(&p) || !json_get_token(&p)) {
+        json_error(&p, "empty input stream");
+    } else if (p.token.type == '{') {
+        json = json_parse_object(&p);
+    } else if (p.token.type == '[') {
+        json = json_parse_array(&p);
+    } else {
+        json_error(&p, "syntax error at beginning of input");
+    }
+    if (p.token.type != T_EOF) {
+        json_error(&p, "trailing garbage at end of input");
+    }
+
+    if (p.error) {
+        json_destroy(json);
+        json = json_create(JSON_STRING);
+        json->u.string = p.error;
+    } else {
+        assert(json);
+    }
+
+    json_token_destroy(&p.token);
+    return json;
+}
+
+static bool
+json_parse_member(struct json_parser *p, void *object_)
+{
+    struct json *object = object_;
+    struct json *value;
+    char *name = NULL;
+
+    /* Parse name. */
+    if (p->token.type != T_STRING) {
+        json_error(p, "syntax error parsing object expecting string");
+        goto error;
+    }
+    name = json_token_steal_string(&p->token);
+
+    /* Parse ':'. */
+    json_get_token(p);
+    if (p->token.type != ':') {
+        json_error(p, "syntax error parsing object expecting ':'");
+        goto error;
+    }
+
+    /* Parse value. */
+    json_get_token(p);
+    value = json_parse_value(p);
+    if (!value) {
+        goto error;
+    }
+
+    /* Add value to object. */
+    if (!shash_find(object->u.object, name)) {
+        shash_add(object->u.object, name, value);
+    } else {
+        /* Duplicate name. */
+        json_destroy(value);
+    }
+    free(name);
+    return true;
+
+error:
+    free(name);
+    return false;
+}
+
+static bool
+json_parse_list(struct json_parser *p, int start_token, int end_token,
+                bool (*parse_cb)(struct json_parser *p, void *aux),
+                void *aux)
+{
+    /* Skip 'start_token'. */
+    assert(p->token.type == start_token);
+    json_get_token(p);
+
+    /* Parse list elements. */
+    if (p->token.type != end_token) {
+        for (;;) {
+            if (!parse_cb(p, aux)) {
+                return false;
+            }
+
+            if (p->token.type == ',') {
+                json_get_token(p);
+            } else if (p->token.type == end_token) {
+                break;
+            } else {
+                json_error(p, "syntax error expecting '%c' or ','", end_token);
+                return false;
+            }
+        }
+    }
+
+    /* Skip 'end_token'. */
+    json_get_token(p);
+
+    return true;
+}
+
+static struct json *
+json_parse_object(struct json_parser *p)
+{
+    struct json *object;
+
+    object = xmalloc(sizeof *object);
+    object->type = JSON_OBJECT;
+    object->u.object = xmalloc(sizeof *object->u.object);
+    shash_init(object->u.object);
+
+    if (!json_parse_list(p, '{', '}', json_parse_member, object)) {
+        json_destroy(object);
+        return NULL;
+    }
+    return object;
+}
+
+struct json_array_parser {
+    struct json *array;
+    size_t n_allocated;
+};
+
+static bool
+json_parse_element(struct json_parser *p, void *ap_)
+{
+    struct json_array_parser *ap = ap_;
+    struct json_array *array = &ap->array->u.array;
+    struct json *value;
+
+    value = json_parse_value(p);
+    if (!value) {
+        return false;
+    }
+
+    if (array->n >= ap->n_allocated) {
+        array->elems = x2nrealloc(array->elems, &ap->n_allocated,
+                                  sizeof *array->elems);
+    }
+    array->elems[array->n++] = value;
+    return true;
+}
+
+static struct json *
+json_parse_array(struct json_parser *p)
+{
+    struct json_array_parser ap;
+    struct json_array *array;
+
+    ap.array = xmalloc(sizeof *ap.array);
+    ap.array->type = JSON_ARRAY;
+    array = &ap.array->u.array;
+    array->n = 0;
+    array->elems = NULL;
+    ap.n_allocated = 0;
+
+    if (!json_parse_list(p, '[', ']', json_parse_element, &ap)) {
+        json_destroy(ap.array);
+        return NULL;
+    }
+    if (ap.n_allocated > array->n) {
+        array->elems = xrealloc(array->elems, sizeof *array->elems * array->n);
+    }
+    return ap.array;
+}
+
+static struct json *
+json_parse_keyword(struct json_parser *p, enum json_type type)
+{
+    json_get_token(p);
+    return json_create(type);
+}
+
+
+static struct json *
+json_parse_value(struct json_parser *p)
+{
+    struct json *json;
+
+    if (p->depth++ > JSON_MAX_DEPTH) {
+        json_error(p, "input exceeds maximum nesting depth %d",
+                   JSON_MAX_DEPTH);
+        return NULL;
+    }
+
+    switch (p->token.type) {
+    case T_FALSE:
+        json = json_parse_keyword(p, JSON_FALSE);
+        break;
+
+    case T_NULL:
+        json = json_parse_keyword(p, JSON_NULL);
+        break;
+
+    case T_TRUE:
+        json = json_parse_keyword(p, JSON_TRUE);
+        break;
+
+    case '{':
+        json = json_parse_object(p);
+        break;
+
+    case '[':
+        json = json_parse_array(p);
+        break;
+
+    case T_INTEGER:
+        json = json_create(JSON_INTEGER);
+        json->u.integer = p->token.u.integer;
+        json_get_token(p);
+        break;
+
+    case T_REAL:
+        json = json_create(JSON_REAL);
+        json->u.real = p->token.u.real;
+        json_get_token(p);
+        break;
+
+    case T_STRING:
+        json = json_create(JSON_STRING);
+        json->u.string = json_token_steal_string(&p->token);
+        json_get_token(p);
+        break;
+
+    case T_EOF:
+    case '}':
+    case ']':
+    case ':':
+    case ',':
+    default:
+        json_error(p, "syntax error expecting value");
+        json = NULL;
+        break;
+    }
+
+    p->depth--;
+
+    return json;
+}
+
+static struct json *
+json_create(enum json_type type)
+{
+    struct json *json = xmalloc(sizeof *json);
+    json->type = type;
+    return json;
+}
+
+static void
+json_error(struct json_parser *p, const char *format, ...)
+{
+    if (!p->error) {
+        va_list args;
+
+        va_start(args, format);
+        p->error = xvasprintf(format, args);
+        va_end(args);
+    }
+}
+
+static void json_to_ds(const struct json *, struct ds *);
+static void json_object_to_ds(const struct shash *object, struct ds *);
+static void json_array_to_ds(const struct json_array *array, struct ds *);
+static void json_string_to_ds(const char *string, struct ds *);
+
+/* Converts 'json' to a string in JSON format, encoded in UTF-8, and returns
+ * that string.  The caller is responsible for freeing the returned string,
+ * with free(), when it is no longer needed.
+ *
+ * The returned string is valid JSON only if 'json' represents an array or an
+ * object, since a bare literal does not satisfy the JSON grammar.
+ *
+ * Members of objects in the output are sorted in bytewise lexicographic order
+ * for reproducibility. */
+char *
+json_to_string(const struct json *json)
+{
+    struct ds ds = DS_EMPTY_INITIALIZER;
+    json_to_ds(json, &ds);
+    return ds_steal_cstr(&ds);
+}
+
+static void
+json_to_ds(const struct json *json, struct ds *ds)
+{
+    switch (json->type) {
+    case JSON_NULL:
+        ds_put_cstr(ds, "null");
+        break;
+
+    case JSON_FALSE:
+        ds_put_cstr(ds, "false");
+        break;
+
+    case JSON_TRUE:
+        ds_put_cstr(ds, "true");
+        break;
+
+    case JSON_OBJECT:
+        json_object_to_ds(json->u.object, ds);
+        break;
+
+    case JSON_ARRAY:
+        json_array_to_ds(&json->u.array, ds);
+        break;
+
+    case JSON_INTEGER:
+        ds_put_format(ds, "%lld", json->u.integer);
+        break;
+
+    case JSON_REAL:
+        ds_put_format(ds, "%.*g", DBL_DIG, json->u.real);
+        break;
+
+    case JSON_STRING:
+        json_string_to_ds(json->u.string, ds);
+        break;
+
+    default:
+        NOT_REACHED();
+    }
+}
+
+static int
+compare_nodes_by_name(const void *a_, const void *b_)
+{
+    const struct shash_node *const *a = a_;
+    const struct shash_node *const *b = b_;
+    return strcmp((*a)->name, (*b)->name);
+}
+
+static void
+json_object_to_ds(const struct shash *object, struct ds *ds)
+{
+    size_t n = shash_count(object);
+    struct shash_node **nodes;
+    struct shash_node *node;
+    size_t i;
+
+    ds_put_char(ds, '{');
+
+    nodes = xmalloc(n * sizeof *nodes);
+    i = 0;
+    SHASH_FOR_EACH (node, object) {
+        nodes[i++] = node;
+    }
+    assert(i == n);
+
+    qsort(nodes, n, sizeof *nodes, compare_nodes_by_name);
+    for (i = 0; i < n; i++) {
+        node = nodes[i];
+
+        if (i) {
+            ds_put_char(ds, ',');
+        }
+
+        json_string_to_ds(node->name, ds);
+        ds_put_char(ds, ':');
+        json_to_ds(node->data, ds);
+    }
+
+    free(nodes);
+
+    ds_put_char(ds, '}');
+}
+
+static void
+json_array_to_ds(const struct json_array *array, struct ds *ds)
+{
+    size_t i;
+
+    ds_put_char(ds, '[');
+    for (i = 0; i < array->n; i++) {
+        if (i) {
+            ds_put_char(ds, ',');
+        }
+        json_to_ds(array->elems[i], ds);
+    }
+    ds_put_char(ds, ']');
+}
+
+static void
+json_string_to_ds(const char *string, struct ds *ds)
+{
+    uint8_t c;
+
+    ds_put_char(ds, '"');
+    while ((c = *string++) != '\0') {
+        switch (c) {
+        case '"':
+            ds_put_cstr(ds, "\\\"");
+            break;
+
+        case '\\':
+            ds_put_cstr(ds, "\\\\");
+            break;
+
+        case '\b':
+            ds_put_cstr(ds, "\\b");
+            break;
+
+        case '\f':
+            ds_put_cstr(ds, "\\f");
+            break;
+
+        case '\n':
+            ds_put_cstr(ds, "\\n");
+            break;
+
+        case '\r':
+            ds_put_cstr(ds, "\\r");
+            break;
+
+        case '\t':
+            ds_put_cstr(ds, "\\t");
+            break;
+
+        default:
+            if (c >= 32) {
+                ds_put_char(ds, c);
+            } else {
+                ds_put_format(ds, "\\u%04x", c);
+            }
+            break;
+        }
+    }
+    ds_put_char(ds, '"');
+}
diff --git a/lib/json.h b/lib/json.h
new file mode 100644
index 0000000..90657d9
--- /dev/null
+++ b/lib/json.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef JSON_H
+#define JSON_H 1
+
+/* This is an implementation of JavaScript Object Notation (JSON) as specified
+ * by RFC 4627.  It is intended to fully comply with RFC 4627, with the
+ * following known exceptions and clarifications:
+ *
+ *      - Null bytes (\u0000) are not allowed in strings.
+ *
+ *      - Only UTF-8 encoding is supported (RFC 4627 allows for other Unicode
+ *        encodings).
+ *
+ *      - Names within an object must be unique (RFC 4627 says that they
+ *        "should" be unique).
+ */
+
+#include "shash.h"
+
+enum json_type {
+    JSON_NULL,                  /* null */
+    JSON_FALSE,                 /* false */
+    JSON_TRUE,                  /* true */
+    JSON_OBJECT,                /* {"a": b, "c": d, ...} */
+    JSON_ARRAY,                 /* [1, 2, 3, ...] */
+    JSON_INTEGER,               /* 123. */
+    JSON_REAL,                  /* 123.456. */
+    JSON_STRING                 /* "..." */
+};
+
+struct json_array {
+    size_t n;
+    struct json **elems;
+};
+
+struct json {
+    enum json_type type;
+    union {
+        struct shash *object;   /* Contains "struct json *"s. */
+        struct json_array array;
+        long long int integer;
+        double real;
+        char *string;
+    } u;
+};
+
+void json_destroy(struct json *);
+
+struct json *json_from_string(const char *string);
+struct json *json_from_file(const char *file_name);
+struct json *json_from_cb(int (*)(void *aux, const char **inputp), void *aux);
+
+char *json_to_string(const struct json *);
+
+#endif /* json.h */
diff --git a/lib/shash.c b/lib/shash.c
index 7bb8cd7..bdaa496 100644
--- a/lib/shash.c
+++ b/lib/shash.c
@@ -57,6 +57,12 @@ shash_is_empty(const struct shash *shash)
     return hmap_is_empty(&shash->map);
 }
 
+size_t
+shash_count(const struct shash *shash)
+{
+    return hmap_count(&shash->map);
+}
+
 /* It is the caller's responsibility to avoid duplicate names, if that is
  * desirable. */
 struct shash_node *
diff --git a/lib/shash.h b/lib/shash.h
index 5794a20..0ce80c4 100644
--- a/lib/shash.h
+++ b/lib/shash.h
@@ -42,6 +42,7 @@ void shash_init(struct shash *);
 void shash_destroy(struct shash *);
 void shash_clear(struct shash *);
 bool shash_is_empty(const struct shash *);
+size_t shash_count(const struct shash *);
 struct shash_node *shash_add(struct shash *, const char *, void *);
 void shash_delete(struct shash *, struct shash_node *);
 struct shash_node *shash_find(const struct shash *, const char *);
diff --git a/lib/unicode.c b/lib/unicode.c
new file mode 100644
index 0000000..69ebcfc
--- /dev/null
+++ b/lib/unicode.c
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "unicode.h"
+
+/* Returns the unicode code point corresponding to leading surrogate 'leading'
+ * and trailing surrogate 'trailing'.  The return value will not make any
+ * sense if 'leading' or 'trailing' are not in the correct ranges for leading
+ * or trailing surrogates. */
+int
+utf16_decode_surrogate_pair(int leading, int trailing)
+{
+    /*
+     *  Leading surrogate:         110110wwwwxxxxxx
+     * Trailing surrogate:         110111xxxxxxxxxx
+     *         Code point: 000uuuuuxxxxxxxxxxxxxxxx
+     */
+    int w = (leading >> 6) & 0xf;
+    int u = w + 1;
+    int x0 = leading & 0x3f;
+    int x1 = trailing & 0x3ff;
+    return (u << 16) | (x0 << 10) | x1;
+}
diff --git a/lib/unicode.h b/lib/unicode.h
new file mode 100644
index 0000000..0f20bdc
--- /dev/null
+++ b/lib/unicode.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef UNICODE_H
+#define UNICODE_H 1
+
+#include <stdbool.h>
+
+/* Returns true if 'c' is a Unicode code point, otherwise false. */
+static inline bool
+uc_is_code_point(int c)
+{
+    return c >= 0 && c <= 0x10ffff;
+}
+
+/* Returns true if 'c' is a Unicode code point for a leading surrogate. */
+static inline bool
+uc_is_leading_surrogate(int c)
+{
+    return c >= 0xd800 && c <= 0xdbff;
+}
+
+/* Returns true if 'c' is a Unicode code point for a trailing surrogate. */
+static inline bool
+uc_is_trailing_surrogate(int c)
+{
+    return c >= 0xdc00 && c <= 0xdfff;
+}
+
+/* Returns true if 'c' is a Unicode code point for a leading or trailing
+ * surrogate. */
+static inline bool
+uc_is_surrogate(int c)
+{
+    return c >= 0xd800 && c <= 0xdfff;
+}
+
+int utf16_decode_surrogate_pair(int leading, int trailing);
+
+#endif /* unicode.h */
diff --git a/lib/util.c b/lib/util.c
index f766d59..56f5199 100644
--- a/lib/util.c
+++ b/lib/util.c
@@ -294,3 +294,58 @@ str_to_ullong(const char *s, int base, unsigned long long *ull)
 {
     return str_to_llong(s, base, (long long *) ull);
 }
+
+/* Converts floating-point string 's' into a double.  If successful, stores
+ * the double in '*d' and returns true; on failure, stores 0 in '*d' and
+ * returns false.
+ *
+ * Underflow (e.g. "1e-9999") is not considered an error, but overflow
+ * (e.g. "1e9999)" is. */
+bool
+str_to_double(const char *s, double *d)
+{
+    int save_errno = errno;
+    char *tail;
+    errno = 0;
+    *d = strtod(s, &tail);
+    if (errno == EINVAL || (errno == ERANGE && *d != 0)
+        || tail == s || *tail != '\0') {
+        errno = save_errno;
+        *d = 0;
+        return false;
+    } else {
+        errno = save_errno;
+        return true;
+    }
+}
+
+/* Returns the value of 'c' as a hexadecimal digit. */
+int
+hexit_value(int c)
+{
+    switch (c) {
+    case '0': case '1': case '2': case '3': case '4':
+    case '5': case '6': case '7': case '8': case '9':
+        return c - '0';
+
+    case 'a': case 'A':
+        return 0xa;
+
+    case 'b': case 'B':
+        return 0xb;
+
+    case 'c': case 'C':
+        return 0xc;
+
+    case 'd': case 'D':
+        return 0xd;
+
+    case 'e': case 'E':
+        return 0xe;
+
+    case 'f': case 'F':
+        return 0xf;
+    }
+
+    NOT_REACHED();
+}
diff --git a/lib/util.h b/lib/util.h
index 962bad2..4c9ed6c 100644
--- a/lib/util.h
+++ b/lib/util.h
@@ -120,6 +120,10 @@ bool str_to_uint(const char *, int base, unsigned int *);
 bool str_to_ulong(const char *, int base, unsigned long *);
 bool str_to_ullong(const char *, int base, unsigned long long *);
 
+bool str_to_double(const char *, double *);
+
+int hexit_value(int c);
+
 #ifdef  __cplusplus
 }
 #endif
diff --git a/tests/.gitignore b/tests/.gitignore
index 706aa14..caa2db6 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -5,6 +5,7 @@
 /test-flows
 /test-hash
 /test-hmap
+/test-json
 /test-list
 /test-stp
 /test-type-props
diff --git a/tests/automake.mk b/tests/automake.mk
index 7659a26..cba914f 100644
--- a/tests/automake.mk
+++ b/tests/automake.mk
@@ -8,6 +8,7 @@ TESTSUITE_AT = \
 	tests/testsuite.at \
 	tests/lcov-pre.at \
 	tests/library.at \
+	tests/json.at \
 	tests/stp.at \
 	tests/ovs-vsctl.at \
 	tests/lcov-post.at
@@ -58,6 +59,10 @@ noinst_PROGRAMS += tests/test-hmap
 tests_test_hmap_SOURCES = tests/test-hmap.c
 tests_test_hmap_LDADD = lib/libopenvswitch.a
 
+noinst_PROGRAMS += tests/test-json
+tests_test_json_SOURCES = tests/test-json.c
+tests_test_json_LDADD = lib/libopenvswitch.a
+
 noinst_PROGRAMS += tests/test-list
 tests_test_list_SOURCES = tests/test-list.c
 tests_test_list_LDADD = lib/libopenvswitch.a
diff --git a/tests/json.at b/tests/json.at
new file mode 100644
index 0000000..224086b
--- /dev/null
+++ b/tests/json.at
@@ -0,0 +1,211 @@
+m4_define([JSON_CHECK_POSITIVE], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([json positive])
+   AT_DATA([input], [$2
+])
+   OVS_CHECK_LCOV([test-json input], [0], [$3
+], [])
+   AT_CLEANUP])
+
+m4_define([JSON_CHECK_NEGATIVE], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([json negative])
+   AT_DATA([input], [$2
+])
+   OVS_CHECK_LCOV([test-json input], [1], [], [error: $3
+])
+   AT_CLEANUP])
+
+AT_BANNER([JSON -- arrays])
+
+JSON_CHECK_POSITIVE([empty array], [[ [   ] ]], [[[]]])
+JSON_CHECK_POSITIVE([single-element array], [[ [ 1 ] ]], [[[1]]])
+JSON_CHECK_POSITIVE([2-element array], [[ [ 1, 2 ] ]], [[[1,2]]])
+JSON_CHECK_POSITIVE([many-element array],
+                    [[ [ 1, 2, 3, 4, 5 ] ]],
+                    [[[1,2,3,4,5]]])
+JSON_CHECK_NEGATIVE([missing comma], [[ [ 1, 2, 3 4, 5 ] ]],
+                    [syntax error expecting '@:>@' or ','])
+JSON_CHECK_NEGATIVE([trailing comma not allowed], 
+                    [[[1,2,]]], [syntax error expecting value])
+JSON_CHECK_NEGATIVE([doubled comma not allowed], 
+                    [[[1,,2]]], [syntax error expecting value])
+
+AT_BANNER([JSON -- strings])
+
+JSON_CHECK_POSITIVE([empty string], [[[ "" ]]], [[[""]]])
+JSON_CHECK_POSITIVE([1-character strings], 
+                    [[[ "a", "b", "c" ]]],
+                    [[["a","b","c"]]])
+JSON_CHECK_POSITIVE([escape sequences], 
+  [[[ " \" \\ \/ \b \f \n \r \t" ]]],
+  [[[" \" \\ / \b \f \n \r \t"]]])
+JSON_CHECK_POSITIVE([Unicode escape sequences], 
+  [[[ " \u0022 \u005c \u002F \u0008 \u000c \u000A \u000d \u0009" ]]],
+  [[[" \" \\ / \b \f \n \r \t"]]])
+JSON_CHECK_POSITIVE([surrogate pairs],
+  [[["\ud834\udd1e"]]],
+  [[["𝄞"]]])
+JSON_CHECK_NEGATIVE([a string by itself is not valid JSON], ["xxx"],
+                    [syntax error at beginning of input])
+JSON_CHECK_NEGATIVE([end of line in quoted string],
+                    [[["xxx
+"]]],
+                    [U+000A must be escaped in quoted string])
+JSON_CHECK_NEGATIVE([formfeed in quoted string],
+                    [[["xxx"]]],
+                    [U+000C must be escaped in quoted string])
+JSON_CHECK_NEGATIVE([bad escape in quoted string],
+                    [[["\x12"]]],
+                    [bad escape \x])
+JSON_CHECK_NEGATIVE([\u must be followed by 4 hex digits],
+                    [[["\u1x"]]],
+                    [malformed \u escape])
+JSON_CHECK_NEGATIVE([isolated leading surrogate not allowed],
+                    [[["\ud834xxx"]]],
+                    [malformed escaped surrogate pair])
+JSON_CHECK_NEGATIVE([surrogatess must paired properly],
+                    [[["\ud834\u1234"]]],
+                    [second half of escaped surrogate pair is not trailing surrogate])
+JSON_CHECK_NEGATIVE([null bytes not allowed], 
+                    [[["\u0000"]]], 
+                    [null bytes not supported in quoted strings])
+
+AT_SETUP([end of input in quoted string])
+AT_KEYWORDS([json negative])
+AT_CHECK([printf '\"xxx' | test-json -], [1], [],
+  [error: unexpected end of input in quoted string
+])
+AT_CLEANUP
+
+AT_BANNER([JSON -- objects])
+
+JSON_CHECK_POSITIVE([empty object], [[{ }]], [[{}]])
+JSON_CHECK_POSITIVE([simple object],
+                    [[{"b": 2, "a": 1, "c": 3}]],
+                    [[{"a":1,"b":2,"c":3}]])
+JSON_CHECK_NEGATIVE([bad value], [[{"a": }, "b": 2]], 
+                    [syntax error expecting value])
+JSON_CHECK_NEGATIVE([missing colon], [[{"b": 2, "a" 1, "c": 3}]],
+                    [syntax error parsing object expecting ':'])
+JSON_CHECK_NEGATIVE([missing comma], [[{"b": 2 "a" 1, "c": 3}]],
+                    [syntax error expecting '}' or ','])
+JSON_CHECK_NEGATIVE([trailing comma not allowed],
+                    [[{"b": 2, "a": 1, "c": 3, }]],
+                    [[syntax error parsing object expecting string]])
+JSON_CHECK_NEGATIVE([doubled comma not allowed],
+                    [[{"b": 2, "a": 1,, "c": 3}]],
+                    [[syntax error parsing object expecting string]])
+JSON_CHECK_NEGATIVE([names must be strings],
+                    [[{1: 2}]],
+                    [[syntax error parsing object expecting string]])
+
+AT_BANNER([JSON -- literal names])
+
+JSON_CHECK_POSITIVE([null], [[[ null ]]], [[[null]]])
+JSON_CHECK_POSITIVE([false], [[[ false ]]], [[[false]]])
+JSON_CHECK_POSITIVE([true], [[[ true ]]], [[[true]]])
+JSON_CHECK_NEGATIVE([a literal by itself is not valid JSON], [null],
+                    [syntax error at beginning of input])
+JSON_CHECK_NEGATIVE([nullify is invalid], [[[ nullify ]]], [invalid keyword])
+JSON_CHECK_NEGATIVE([nubs is invalid], [[[ nubs ]]], [invalid keyword])
+JSON_CHECK_NEGATIVE([xxx is invalid], [[[ xxx ]]], [invalid character 'x'])
+
+AT_BANNER([JSON -- numbers])
+
+JSON_CHECK_POSITIVE(
+  [reals], 
+  [[[0.0, 1.0, 2.0, 3.0, 3.5, 8.1250]]],
+  [[[0,1,2,3,3.5,8.125]]])
+JSON_CHECK_POSITIVE(
+  [scientific notation],
+  [[[1e3, 1E3, 2.5E2, 1e+3, 125e-3, 3.125e-2, 3125e-05, 1.525878906e-5]]],
+  [[[1000,1000,250,1000,0.125,0.03125,0.03125,1.525878906e-05]]])
+JSON_CHECK_POSITIVE(
+  [negative reals], 
+  [[[-0, -1.0, -2.0, -3.0, -3.5, -8.1250]]],
+  [[[0,-1,-2,-3,-3.5,-8.125]]])
+JSON_CHECK_POSITIVE(
+  [negative scientific notation],
+  [[[-1e3, -1E3, -2.5E2, -1e+3, -125e-3, -3.125e-2, -3125e-05, -1.525878906e-5]]],
+  [[[-1000,-1000,-250,-1000,-0.125,-0.03125,-0.03125,-1.525878906e-05]]])
+JSON_CHECK_POSITIVE(
+  [1e-9999 underflows to 0],
+  [[[1e-9999]]],
+  [[[0]]])
+JSON_CHECK_NEGATIVE([a number by itself is not valid JSON], [1],
+                    [syntax error at beginning of input])
+JSON_CHECK_NEGATIVE(
+  [leading zeros not allowed],
+  [[[0123]]],
+  [leading zeros not allowed])
+JSON_CHECK_NEGATIVE(
+  [1e9999 is too big],
+  [[[1e9999]]],
+  [number "1e9999" outside valid range])
+
+AT_BANNER([JSON -- RFC 4627 examples])
+
+JSON_CHECK_POSITIVE([RFC 4267 object example],
+[[{
+   "Image": {
+       "Width":  800,
+       "Height": 600,
+       "Title":  "View from 15th Floor",
+       "Thumbnail": {
+           "Url":    "http://www.example.com/image/481989943",
+           "Height": 125,
+           "Width":  "100"
+       },
+       "IDs": [116, 943, 234, 38793]
+     }
+}]],
+[[{"Image":{"Height":600,"IDs":[116,943,234,38793],"Thumbnail":{"Height":125,"Url":"http://www.example.com/image/481989943","Width":"100"},"Title":"View from 15th Floor","Width":800}}]])
+
+JSON_CHECK_POSITIVE([RFC 4267 array example],
+[[[
+   {
+      "precision": "zip",
+      "Latitude":  37.7668,
+      "Longitude": -122.3959,
+      "Address":   "",
+      "City":      "SAN FRANCISCO",
+      "State":     "CA",
+      "Zip":       "94107",
+      "Country":   "US"
+   },
+   {
+      "precision": "zip",
+      "Latitude":  37.371991,
+      "Longitude": -122.026020,
+      "Address":   "",
+      "City":      "SUNNYVALE",
+      "State":     "CA",
+      "Zip":       "94085",
+      "Country":   "US"
+   }
+]]],
+[[[{"Address":"","City":"SAN FRANCISCO","Country":"US","Latitude":37.7668,"Longitude":-122.3959,"State":"CA","Zip":"94107","precision":"zip"},{"Address":"","City":"SUNNYVALE","Country":"US","Latitude":37.371991,"Longitude":-122.02602,"State":"CA","Zip":"94085","precision":"zip"}]]])
+
+AT_BANNER([JSON -- pathological cases])
+
+JSON_CHECK_NEGATIVE([trailing garbage], [[[1]null]],
+                    [trailing garbage at end of input])
+JSON_CHECK_NEGATIVE([formfeeds are not valid white space],
+                    [[[]]], [invalid character U+000c])
+JSON_CHECK_NEGATIVE([';' is not a valid token],
+                    [;], [invalid character ';'])
+JSON_CHECK_NEGATIVE([arrays nesting too deep],
+                    [m4_for([i], [0], [1002], [1], [@<:@])dnl
+                     m4_for([i], [0], [1002], [1], [@:>@])],
+                    [input exceeds maximum nesting depth 1000])
+JSON_CHECK_NEGATIVE([objects nesting too deep],
+                    [m4_for([i], [0], [1002], [1], [{"x":])dnl
+                     m4_for([i], [0], [1002], [1], [}])],
+                    [input exceeds maximum nesting depth 1000])
+
+AT_SETUP([input may not be empty])
+AT_KEYWORDS([json negative])
+AT_CHECK([test-json /dev/null], [1], [], [error: empty input stream
+])
+AT_CLEANUP
diff --git a/tests/test-json.c b/tests/test-json.c
new file mode 100644
index 0000000..9f9a1eb
--- /dev/null
+++ b/tests/test-json.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "json.h"
+
+#include <stdio.h>
+
+int
+main(int argc, char *argv[])
+{
+    struct json *json;
+    int exit_code;
+
+    if (argc != 2) {
+        ovs_fatal(0, "usage: %s INPUT.json", argv[0]);
+    }
+
+    json = json_from_file(!strcmp(argv[1], "-") ? "/dev/stdin" : argv[1]);
+    if (json->type == JSON_STRING) {
+        fprintf(stderr, "error: %s\n", json->u.string);
+        exit_code = 1;
+    } else {
+        char *s = json_to_string(json);
+        puts(s);
+        free(s);
+        exit_code = 0;
+    }
+    json_destroy(json);
+    return exit_code;
+}
diff --git a/tests/testsuite.at b/tests/testsuite.at
index c232a87..dba19af 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -19,6 +19,7 @@ AT_TESTED([ovs-vsctl])
 
 m4_include([tests/lcov-pre.at])
 m4_include([tests/library.at])
+m4_include([tests/json.at])
 m4_include([tests/stp.at])
 m4_include([tests/ovs-vsctl.at])
 m4_include([tests/lcov-post.at])
-- 
1.6.3.3





More information about the dev mailing list