summaryrefslogtreecommitdiffstats
path: root/src/json.c
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2016-02-07 19:19:53 +0100
committerBram Moolenaar <Bram@vim.org>2016-02-07 19:19:53 +0100
commit595e64e259faefb330866852e1b9f6168544572a (patch)
tree87986bc108647e7c597195cea325ca130db69a40 /src/json.c
parent55fab439a6f3bba6dbe780ac034b84d5822a1a96 (diff)
patch 7.4.1279v7.4.1279
Problem: jsonencode() is not producing strict JSON. Solution: Add jsencode() and jsdecode(). Make jsonencode() and jsondecode() strict.
Diffstat (limited to 'src/json.c')
-rw-r--r--src/json.c138
1 files changed, 94 insertions, 44 deletions
diff --git a/src/json.c b/src/json.c
index 17eed4fa10..31bac26d5f 100644
--- a/src/json.c
+++ b/src/json.c
@@ -16,22 +16,23 @@
#include "vim.h"
#if defined(FEAT_EVAL) || defined(PROTO)
-static int json_encode_item(garray_T *gap, typval_T *val, int copyID, int allow_none);
-static int json_decode_item(js_read_T *reader, typval_T *res);
+static int json_encode_item(garray_T *gap, typval_T *val, int copyID, int options);
+static int json_decode_item(js_read_T *reader, typval_T *res, int options);
/*
* Encode "val" into a JSON format string.
* The result is in allocated memory.
* The result is empty when encoding fails.
+ * "options" can be JSON_JS or zero;
*/
char_u *
-json_encode(typval_T *val)
+json_encode(typval_T *val, int options)
{
garray_T ga;
/* Store bytes in the growarray. */
ga_init2(&ga, 1, 4000);
- if (json_encode_item(&ga, val, get_copyID(), TRUE) == FAIL)
+ if (json_encode_item(&ga, val, get_copyID(), options) == FAIL)
{
vim_free(ga.ga_data);
return vim_strsave((char_u *)"");
@@ -41,10 +42,11 @@ json_encode(typval_T *val)
/*
* Encode ["nr", "val"] into a JSON format string in allocated memory.
+ * "options" can be JSON_JS or zero;
* Returns NULL when out of memory.
*/
char_u *
-json_encode_nr_expr(int nr, typval_T *val)
+json_encode_nr_expr(int nr, typval_T *val, int options)
{
typval_T listtv;
typval_T nrtv;
@@ -61,7 +63,7 @@ json_encode_nr_expr(int nr, typval_T *val)
return NULL;
}
- text = json_encode(&listtv);
+ text = json_encode(&listtv, options);
list_unref(listtv.vval.v_list);
return text;
}
@@ -123,11 +125,29 @@ write_string(garray_T *gap, char_u *str)
}
/*
+ * Return TRUE if "key" can be used without quotes.
+ * That is when it starts with a letter and only contains letters, digits and
+ * underscore.
+ */
+ static int
+is_simple_key(char_u *key)
+{
+ char_u *p;
+
+ if (!ASCII_ISALPHA(*key))
+ return FALSE;
+ for (p = key + 1; *p != NUL; ++p)
+ if (!ASCII_ISALPHA(*p) && *p != '_' && !vim_isdigit(*p))
+ return FALSE;
+ return TRUE;
+}
+
+/*
* Encode "val" into "gap".
* Return FAIL or OK.
*/
static int
-json_encode_item(garray_T *gap, typval_T *val, int copyID, int allow_none)
+json_encode_item(garray_T *gap, typval_T *val, int copyID, int options)
{
char_u numbuf[NUMBUFLEN];
char_u *res;
@@ -141,13 +161,11 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID, int allow_none)
{
case VVAL_FALSE: ga_concat(gap, (char_u *)"false"); break;
case VVAL_TRUE: ga_concat(gap, (char_u *)"true"); break;
- case VVAL_NONE: if (!allow_none)
- {
- /* TODO: better error */
- EMSG(_(e_invarg));
- return FAIL;
- }
- break;
+ case VVAL_NONE: if ((options & JSON_JS) != 0
+ && (options & JSON_NO_NONE) == 0)
+ /* empty item */
+ break;
+ /* FALLTHROUGH */
case VVAL_NULL: ga_concat(gap, (char_u *)"null"); break;
}
break;
@@ -185,9 +203,15 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID, int allow_none)
ga_append(gap, '[');
for (li = l->lv_first; li != NULL && !got_int; )
{
- if (json_encode_item(gap, &li->li_tv, copyID, TRUE)
- == FAIL)
+ if (json_encode_item(gap, &li->li_tv, copyID,
+ options & JSON_JS) == FAIL)
return FAIL;
+ if ((options & JSON_JS)
+ && li->li_next == NULL
+ && li->li_tv.v_type == VAR_SPECIAL
+ && li->li_tv.vval.v_number == VVAL_NONE)
+ /* add an extra comma if the last item is v:none */
+ ga_append(gap, ',');
li = li->li_next;
if (li != NULL)
ga_append(gap, ',');
@@ -224,10 +248,14 @@ json_encode_item(garray_T *gap, typval_T *val, int copyID, int allow_none)
first = FALSE;
else
ga_append(gap, ',');
- write_string(gap, hi->hi_key);
+ if ((options & JSON_JS)
+ && is_simple_key(hi->hi_key))
+ ga_concat(gap, hi->hi_key);
+ else
+ write_string(gap, hi->hi_key);
ga_append(gap, ':');
if (json_encode_item(gap, &dict_lookup(hi)->di_tv,
- copyID, FALSE) == FAIL)
+ copyID, options | JSON_NO_NONE) == FAIL)
return FAIL;
}
ga_append(gap, '}');
@@ -265,7 +293,8 @@ fill_numbuflen(js_read_T *reader)
}
/*
- * Skip white space in "reader".
+ * Skip white space in "reader". All characters <= space are considered white
+ * space.
* Also tops up readahead when needed.
*/
static void
@@ -282,7 +311,7 @@ json_skip_white(js_read_T *reader)
reader->js_end = reader->js_buf + STRLEN(reader->js_buf);
continue;
}
- if (c != ' ' && c != TAB && c != NL && c != CAR)
+ if (c == NUL || c > ' ')
break;
++reader->js_used;
}
@@ -290,7 +319,7 @@ json_skip_white(js_read_T *reader)
}
static int
-json_decode_array(js_read_T *reader, typval_T *res)
+json_decode_array(js_read_T *reader, typval_T *res, int options)
{
char_u *p;
typval_T item;
@@ -317,7 +346,7 @@ json_decode_array(js_read_T *reader, typval_T *res)
break;
}
- ret = json_decode_item(reader, res == NULL ? NULL : &item);
+ ret = json_decode_item(reader, res == NULL ? NULL : &item, options);
if (ret != OK)
return ret;
if (res != NULL)
@@ -347,7 +376,7 @@ json_decode_array(js_read_T *reader, typval_T *res)
}
static int
-json_decode_object(js_read_T *reader, typval_T *res)
+json_decode_object(js_read_T *reader, typval_T *res, int options)
{
char_u *p;
typval_T tvkey;
@@ -377,16 +406,31 @@ json_decode_object(js_read_T *reader, typval_T *res)
break;
}
- ret = json_decode_item(reader, res == NULL ? NULL : &tvkey);
- if (ret != OK)
- return ret;
- if (res != NULL)
+ if ((options & JSON_JS) && reader->js_buf[reader->js_used] != '"')
{
- key = get_tv_string_buf_chk(&tvkey, buf);
- if (key == NULL || *key == NUL)
+ /* accept a key that is not in quotes */
+ key = p = reader->js_buf + reader->js_used;
+ while (*p != NUL && *p != ':' && *p > ' ')
+ ++p;
+ tvkey.v_type = VAR_STRING;
+ tvkey.vval.v_string = vim_strnsave(key, (int)(p - key));
+ reader->js_used += (int)(p - key);
+ key = tvkey.vval.v_string;
+ }
+ else
+ {
+ ret = json_decode_item(reader, res == NULL ? NULL : &tvkey,
+ options);
+ if (ret != OK)
+ return ret;
+ if (res != NULL)
{
- clear_tv(&tvkey);
- return FAIL;
+ key = get_tv_string_buf_chk(&tvkey, buf);
+ if (key == NULL || *key == NUL)
+ {
+ clear_tv(&tvkey);
+ return FAIL;
+ }
}
}
@@ -403,7 +447,7 @@ json_decode_object(js_read_T *reader, typval_T *res)
++reader->js_used;
json_skip_white(reader);
- ret = json_decode_item(reader, res == NULL ? NULL : &item);
+ ret = json_decode_item(reader, res == NULL ? NULL : &item, options);
if (ret != OK)
{
if (res != NULL)
@@ -569,7 +613,7 @@ json_decode_string(js_read_T *reader, typval_T *res)
* Return MAYBE for an incomplete message.
*/
static int
-json_decode_item(js_read_T *reader, typval_T *res)
+json_decode_item(js_read_T *reader, typval_T *res, int options)
{
char_u *p;
int len;
@@ -579,15 +623,18 @@ json_decode_item(js_read_T *reader, typval_T *res)
switch (*p)
{
case '[': /* array */
- return json_decode_array(reader, res);
+ return json_decode_array(reader, res, options);
case '{': /* object */
- return json_decode_object(reader, res);
+ return json_decode_object(reader, res, options);
case '"': /* string */
return json_decode_string(reader, res);
case ',': /* comma: empty item */
+ if ((options & JSON_JS) == 0)
+ return FAIL;
+ /* FALLTHROUGH */
case NUL: /* empty */
if (res != NULL)
{
@@ -691,17 +738,18 @@ json_decode_item(js_read_T *reader, typval_T *res)
/*
* Decode the JSON from "reader" and store the result in "res".
+ * "options" can be JSON_JS or zero;
* Return FAIL if not the whole message was consumed.
*/
int
-json_decode_all(js_read_T *reader, typval_T *res)
+json_decode_all(js_read_T *reader, typval_T *res, int options)
{
int ret;
- /* We get the end once, to avoid calling strlen() many times. */
+ /* We find the end once, to avoid calling strlen() many times. */
reader->js_end = reader->js_buf + STRLEN(reader->js_buf);
json_skip_white(reader);
- ret = json_decode_item(reader, res);
+ ret = json_decode_item(reader, res, options);
if (ret != OK)
return FAIL;
json_skip_white(reader);
@@ -712,18 +760,19 @@ json_decode_all(js_read_T *reader, typval_T *res)
/*
* Decode the JSON from "reader" and store the result in "res".
+ * "options" can be JSON_JS or zero;
* Return FAIL if the message has a decoding error or the message is
* truncated. Consumes the message anyway.
*/
int
-json_decode(js_read_T *reader, typval_T *res)
+json_decode(js_read_T *reader, typval_T *res, int options)
{
int ret;
- /* We get the end once, to avoid calling strlen() many times. */
+ /* We find the end once, to avoid calling strlen() many times. */
reader->js_end = reader->js_buf + STRLEN(reader->js_buf);
json_skip_white(reader);
- ret = json_decode_item(reader, res);
+ ret = json_decode_item(reader, res, options);
json_skip_white(reader);
return ret == OK ? OK : FAIL;
@@ -731,6 +780,7 @@ json_decode(js_read_T *reader, typval_T *res)
/*
* Decode the JSON from "reader" to find the end of the message.
+ * "options" can be JSON_JS or zero;
* Return FAIL if the message has a decoding error.
* Return MAYBE if the message is truncated, need to read more.
* This only works reliable if the message contains an object, array or
@@ -738,15 +788,15 @@ json_decode(js_read_T *reader, typval_T *res)
* Does not advance the reader.
*/
int
-json_find_end(js_read_T *reader)
+json_find_end(js_read_T *reader, int options)
{
int used_save = reader->js_used;
int ret;
- /* We get the end once, to avoid calling strlen() many times. */
+ /* We find the end once, to avoid calling strlen() many times. */
reader->js_end = reader->js_buf + STRLEN(reader->js_buf);
json_skip_white(reader);
- ret = json_decode_item(reader, NULL);
+ ret = json_decode_item(reader, NULL, options);
reader->js_used = used_save;
return ret;
}