/*
- * Copyright (c) 2009 Nicira Networks.
+ * Copyright (c) 2009, 2010, 2011, 2012, 2013 Nicira, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
#include "json.h"
-#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <float.h>
#include <limits.h>
-#include <math.h>
#include <string.h>
#include "dynamic-string.h"
void
json_object_put(struct json *json, const char *name, struct json *value)
{
- shash_add(json->u.object, name, value);
+ json_destroy(shash_replace(json->u.object, name, value));
}
void
const char *
json_string(const struct json *json)
{
- assert(json->type == JSON_STRING);
+ ovs_assert(json->type == JSON_STRING);
return json->u.string;
}
struct json_array *
json_array(const struct json *json)
{
- assert(json->type == JSON_ARRAY);
- return (struct json_array *) &json->u.array;
+ ovs_assert(json->type == JSON_ARRAY);
+ return CONST_CAST(struct json_array *, &json->u.array);
}
struct shash *
json_object(const struct json *json)
{
- assert(json->type == JSON_OBJECT);
- return (struct shash *) json->u.object;
+ ovs_assert(json->type == JSON_OBJECT);
+ return CONST_CAST(struct shash *, json->u.object);
}
bool
json_boolean(const struct json *json)
{
- assert(json->type == JSON_TRUE || json->type == JSON_FALSE);
+ ovs_assert(json->type == JSON_TRUE || json->type == JSON_FALSE);
return json->type == JSON_TRUE;
}
double
json_real(const struct json *json)
{
- assert(json->type == JSON_REAL || json->type == JSON_INTEGER);
+ ovs_assert(json->type == JSON_REAL || json->type == JSON_INTEGER);
return json->type == JSON_REAL ? json->u.real : json->u.integer;
}
int64_t
json_integer(const struct json *json)
{
- assert(json->type == JSON_INTEGER);
+ ovs_assert(json->type == JSON_INTEGER);
return json->u.integer;
}
\f
const char *cp = ds_cstr(&p->buffer);
unsigned long long int significand = 0;
struct json_token token;
- int sig_digits = 0;
bool imprecise = false;
bool negative = false;
int pow10 = 0;
/* At least one integer digit, but 0 may not be used as a leading digit for
* a longer number. */
significand = 0;
- sig_digits = 0;
if (*cp == '0') {
cp++;
if (isdigit(*cp)) {
do {
if (significand <= ULLONG_MAX / 10) {
significand = significand * 10 + (*cp - '0');
- sig_digits++;
} else {
pow10++;
if (*cp != '0') {
do {
if (significand <= ULLONG_MAX / 10) {
significand = significand * 10 + (*cp - '0');
- sig_digits++;
pow10--;
} else if (*cp != '0') {
imprecise = true;
*
* We suppress negative zeros as a matter of policy. */
if (!significand) {
- struct json_token token;
token.type = T_INTEGER;
token.u.integer = 0;
json_parser_input(p, &token);
if (!imprecise) {
while (pow10 > 0 && significand < ULLONG_MAX / 10) {
significand *= 10;
- sig_digits++;
pow10--;
}
while (pow10 < 0 && significand % 10 == 0) {
significand /= 10;
- sig_digits--;
pow10++;
}
if (pow10 == 0
json_parser_input(p, &token);
}
-static bool
-json_lex_4hex(struct json_parser *p, const char *cp, int *valuep)
+static const char *
+json_lex_4hex(const char *cp, const char *end, int *valuep)
{
- int value, i;
+ unsigned int value;
- value = 0;
- for (i = 0; i < 4; i++) {
- unsigned char c = *cp++;
- if (!isxdigit(c)) {
- json_error(p, "malformed \\u escape");
- return false;
- }
- value = (value << 4) | hexit_value(c);
+ if (cp + 4 > end) {
+ return "quoted string ends within \\u escape";
+ }
+
+ value = hexits_value(cp, 4, NULL);
+ if (value == UINT_MAX) {
+ return "malformed \\u escape";
}
if (!value) {
- json_error(p, "null bytes not supported in quoted strings");
- return false;
+ return "null bytes not supported in quoted strings";
}
*valuep = value;
- return true;
+ return NULL;
}
static const char *
-json_lex_unicode(struct json_parser *p, const char *cp, struct ds *s)
+json_lex_unicode(const char *cp, const char *end, struct ds *out)
{
+ const char *error;
int c0, c1;
- if (!json_lex_4hex(p, cp, &c0)) {
+ error = json_lex_4hex(cp, end, &c0);
+ if (error) {
+ ds_clear(out);
+ ds_put_cstr(out, error);
return NULL;
}
cp += 4;
if (!uc_is_leading_surrogate(c0)) {
- ds_put_utf8(s, c0);
+ ds_put_utf8(out, c0);
return cp;
}
- if (*cp++ != '\\' || *cp++ != 'u') {
- json_error(p, "malformed escaped surrogate pair");
+ if (cp + 2 > end || *cp++ != '\\' || *cp++ != 'u') {
+ ds_clear(out);
+ ds_put_cstr(out, "malformed escaped surrogate pair");
return NULL;
}
- if (!json_lex_4hex(p, cp, &c1)) {
+ error = json_lex_4hex(cp, end, &c1);
+ if (error) {
+ ds_clear(out);
+ ds_put_cstr(out, error);
return NULL;
}
cp += 4;
if (!uc_is_trailing_surrogate(c1)) {
- json_error(p, "second half of escaped surrogate pair is not "
- "trailing surrogate");
+ ds_clear(out);
+ ds_put_cstr(out, "second half of escaped surrogate pair is not "
+ "trailing surrogate");
return NULL;
}
- ds_put_utf8(s, utf16_decode_surrogate_pair(c0, c1));
+ ds_put_utf8(out, utf16_decode_surrogate_pair(c0, c1));
return cp;
}
-static void
-json_lex_string(struct json_parser *p)
+bool
+json_string_unescape(const char *in, size_t in_len, char **outp)
{
- struct json_token token;
- const char *cp;
- struct ds s;
+ const char *end = in + in_len;
+ bool ok = false;
+ struct ds out;
- cp = ds_cstr(&p->buffer);
- if (!strchr(cp, '\\')) {
- token.type = T_STRING;
- token.u.string = cp;
- json_parser_input(p, &token);
- return;
+ ds_init(&out);
+ ds_reserve(&out, in_len);
+ if (in_len > 0 && in[in_len - 1] == '\\') {
+ ds_put_cstr(&out, "quoted string may not end with backslash");
+ goto exit;
}
-
- ds_init(&s);
- ds_reserve(&s, strlen(cp));
- while (*cp != '\0') {
- if (*cp != '\\') {
- ds_put_char(&s, *cp++);
+ while (in < end) {
+ if (*in == '"') {
+ ds_clear(&out);
+ ds_put_cstr(&out, "quoted string may not include unescaped \"");
+ goto exit;
+ }
+ if (*in != '\\') {
+ ds_put_char(&out, *in++);
continue;
}
- cp++;
- switch (*cp++) {
+ in++;
+ switch (*in++) {
case '"': case '\\': case '/':
- ds_put_char(&s, cp[-1]);
+ ds_put_char(&out, in[-1]);
break;
case 'b':
- ds_put_char(&s, '\b');
+ ds_put_char(&out, '\b');
break;
case 'f':
- ds_put_char(&s, '\f');
+ ds_put_char(&out, '\f');
break;
case 'n':
- ds_put_char(&s, '\n');
+ ds_put_char(&out, '\n');
break;
case 'r':
- ds_put_char(&s, '\r');
+ ds_put_char(&out, '\r');
break;
case 't':
- ds_put_char(&s, '\t');
+ ds_put_char(&out, '\t');
break;
case 'u':
- cp = json_lex_unicode(p, cp, &s);
- if (!cp) {
+ in = json_lex_unicode(in, end, &out);
+ if (!in) {
goto exit;
}
break;
default:
- json_error(p, "bad escape \\%c", cp[-1]);
+ ds_clear(&out);
+ ds_put_format(&out, "bad escape \\%c", in[-1]);
goto exit;
}
}
+ ok = true;
+
+exit:
+ *outp = ds_cstr(&out);
+ return ok;
+}
+
+static void
+json_parser_input_string(struct json_parser *p, const char *s)
+{
+ struct json_token token;
token.type = T_STRING;
- token.u.string = ds_cstr(&s);
+ token.u.string = s;
json_parser_input(p, &token);
+}
-exit:
- ds_destroy(&s);
- return;
+static void
+json_lex_string(struct json_parser *p)
+{
+ const char *raw = ds_cstr(&p->buffer);
+ if (!strchr(raw, '\\')) {
+ json_parser_input_string(p, raw);
+ } else {
+ char *cooked;
+
+ if (json_string_unescape(raw, strlen(raw), &cooked)) {
+ json_parser_input_string(p, cooked);
+ } else {
+ json_error(p, "%s", cooked);
+ }
+
+ free(cooked);
+ }
}
static bool
{
struct json_token token;
- p->byte_number++;
- if (c == '\n') {
- p->column_number = 0;
- p->line_number++;
- } else {
- p->column_number++;
- }
-
switch (p->lex_state) {
case JSON_LEX_START:
switch (c) {
size_t i;
for (i = 0; !p->done && i < n; ) {
if (json_lex_input(p, input[i])) {
+ p->byte_number++;
+ if (input[i] == '\n') {
+ p->column_number = 0;
+ p->line_number++;
+ } else {
+ p->column_number++;
+ }
i++;
}
}
}
if (!p->error) {
- assert(p->height == 1);
- assert(p->stack[0].json != NULL);
+ ovs_assert(p->height == 1);
+ ovs_assert(p->stack[0].json != NULL);
json = p->stack[--p->height].json;
} else {
json = json_string_create_nocopy(p->error);
}
}
-static struct json_parser_node *
+static void
json_parser_push(struct json_parser *p,
struct json *new_json, enum json_parse_state new_state)
{
node = &p->stack[p->height++];
node->json = new_json;
p->parse_state = new_state;
- return node;
} else {
+ json_destroy(new_json);
json_error(p, "input exceeds maximum nesting depth %d",
JSON_MAX_HEIGHT);
- return NULL;
}
}
#define SPACES_PER_LEVEL 2
struct json_serializer {
- struct ds ds;
+ struct ds *ds;
int depth;
int flags;
};
-static void json_to_ds(const struct json *, struct json_serializer *);
-static void json_object_to_ds(const struct shash *object,
- struct json_serializer *);
-static void json_array_to_ds(const struct json_array *,
- struct json_serializer *);
-static void json_string_to_ds(const char *string, struct ds *);
+static void json_serialize(const struct json *, struct json_serializer *);
+static void json_serialize_object(const struct shash *object,
+ struct json_serializer *);
+static void json_serialize_array(const struct json_array *,
+ struct json_serializer *);
+static void json_serialize_string(const char *, 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,
* object, since a bare literal does not satisfy the JSON grammar. */
char *
json_to_string(const struct json *json, int flags)
+{
+ struct ds ds;
+
+ ds_init(&ds);
+ json_to_ds(json, flags, &ds);
+ return ds_steal_cstr(&ds);
+}
+
+/* Same as json_to_string(), but the output is appended to 'ds'. */
+void
+json_to_ds(const struct json *json, int flags, struct ds *ds)
{
struct json_serializer s;
- ds_init(&s.ds);
+
+ s.ds = ds;
s.depth = 0;
s.flags = flags;
- json_to_ds(json, &s);
- return ds_steal_cstr(&s.ds);
+ json_serialize(json, &s);
}
static void
-json_to_ds(const struct json *json, struct json_serializer *s)
+json_serialize(const struct json *json, struct json_serializer *s)
{
- struct ds *ds = &s->ds;
+ struct ds *ds = s->ds;
switch (json->type) {
case JSON_NULL:
break;
case JSON_OBJECT:
- json_object_to_ds(json->u.object, s);
+ json_serialize_object(json->u.object, s);
break;
case JSON_ARRAY:
- json_array_to_ds(&json->u.array, s);
+ json_serialize_array(&json->u.array, s);
break;
case JSON_INTEGER:
break;
case JSON_STRING:
- json_string_to_ds(json->u.string, ds);
+ json_serialize_string(json->u.string, ds);
break;
case JSON_N_TYPES:
indent_line(struct json_serializer *s)
{
if (s->flags & JSSF_PRETTY) {
- ds_put_char(&s->ds, '\n');
- ds_put_char_multiple(&s->ds, ' ', SPACES_PER_LEVEL * s->depth);
+ ds_put_char(s->ds, '\n');
+ ds_put_char_multiple(s->ds, ' ', SPACES_PER_LEVEL * s->depth);
}
}
static void
-json_object_member_to_ds(size_t i, const struct shash_node *node,
- struct json_serializer *s)
+json_serialize_object_member(size_t i, const struct shash_node *node,
+ struct json_serializer *s)
{
- struct ds *ds = &s->ds;
+ struct ds *ds = s->ds;
if (i) {
ds_put_char(ds, ',');
indent_line(s);
}
- json_string_to_ds(node->name, ds);
+ json_serialize_string(node->name, ds);
ds_put_char(ds, ':');
if (s->flags & JSSF_PRETTY) {
ds_put_char(ds, ' ');
}
- json_to_ds(node->data, s);
+ json_serialize(node->data, s);
}
static void
-json_object_to_ds(const struct shash *object, struct json_serializer *s)
+json_serialize_object(const struct shash *object, struct json_serializer *s)
{
- struct ds *ds = &s->ds;
+ struct ds *ds = s->ds;
ds_put_char(ds, '{');
nodes = shash_sort(object);
n = shash_count(object);
for (i = 0; i < n; i++) {
- json_object_member_to_ds(i, nodes[i], s);
+ json_serialize_object_member(i, nodes[i], s);
}
free(nodes);
} else {
i = 0;
SHASH_FOR_EACH (node, object) {
- json_object_member_to_ds(i++, node, s);
+ json_serialize_object_member(i++, node, s);
}
}
}
static void
-json_array_to_ds(const struct json_array *array, struct json_serializer *s)
+json_serialize_array(const struct json_array *array, struct json_serializer *s)
{
- struct ds *ds = &s->ds;
+ struct ds *ds = s->ds;
size_t i;
ds_put_char(ds, '[');
ds_put_char(ds, ',');
indent_line(s);
}
- json_to_ds(array->elems[i], s);
+ json_serialize(array->elems[i], s);
}
}
}
static void
-json_string_to_ds(const char *string, struct ds *ds)
+json_serialize_string(const char *string, struct ds *ds)
{
uint8_t c;
}
ds_put_char(ds, '"');
}
+\f
+static size_t
+json_string_serialized_length(const char *string)
+{
+ size_t length;
+ uint8_t c;
+
+ length = strlen("\"\"");
+
+ while ((c = *string++) != '\0') {
+ switch (c) {
+ case '"':
+ case '\\':
+ case '\b':
+ case '\f':
+ case '\n':
+ case '\r':
+ case '\t':
+ length += 2;
+ break;
+
+ default:
+ if (c >= 32) {
+ length++;
+ } else {
+ /* \uXXXX */
+ length += 6;
+ }
+ break;
+ }
+ }
+
+ return length;
+}
+
+static size_t
+json_object_serialized_length(const struct shash *object)
+{
+ size_t length = strlen("{}");
+
+ if (!shash_is_empty(object)) {
+ struct shash_node *node;
+
+ /* Commas and colons. */
+ length += 2 * shash_count(object) - 1;
+
+ SHASH_FOR_EACH (node, object) {
+ const struct json *value = node->data;
+
+ length += json_string_serialized_length(node->name);
+ length += json_serialized_length(value);
+ }
+ }
+
+ return length;
+}
+
+static size_t
+json_array_serialized_length(const struct json_array *array)
+{
+ size_t length = strlen("[]");
+
+ if (array->n) {
+ size_t i;
+
+ /* Commas. */
+ length += array->n - 1;
+
+ for (i = 0; i < array->n; i++) {
+ length += json_serialized_length(array->elems[i]);
+ }
+ }
+
+ return length;
+}
+
+/* Returns strlen(json_to_string(json, 0)), that is, the number of bytes in the
+ * JSON output by json_to_string() for 'json' when JSSF_PRETTY is not
+ * requested. (JSSF_SORT does not affect the length of json_to_string()'s
+ * output.) */
+size_t
+json_serialized_length(const struct json *json)
+{
+ switch (json->type) {
+ case JSON_NULL:
+ return strlen("null");
+
+ case JSON_FALSE:
+ return strlen("false");
+
+ case JSON_TRUE:
+ return strlen("true");
+
+ case JSON_OBJECT:
+ return json_object_serialized_length(json->u.object);
+
+ case JSON_ARRAY:
+ return json_array_serialized_length(&json->u.array);
+
+ case JSON_INTEGER:
+ return snprintf(NULL, 0, "%lld", json->u.integer);
+
+ case JSON_REAL:
+ return snprintf(NULL, 0, "%.*g", DBL_DIG, json->u.real);
+
+ case JSON_STRING:
+ return json_string_serialized_length(json->u.string);
+
+ case JSON_N_TYPES:
+ default:
+ NOT_REACHED();
+ }
+}