changeset 7967:45ea5ebf3a98 v7.4.1279

commit https://github.com/vim/vim/commit/595e64e259faefb330866852e1b9f6168544572a Author: Bram Moolenaar <Bram@vim.org> Date: Sun Feb 7 19:19:53 2016 +0100 patch 7.4.1279 Problem: jsonencode() is not producing strict JSON. Solution: Add jsencode() and jsdecode(). Make jsonencode() and jsondecode() strict.
author Christian Brabandt <cb@256bit.org>
date Sun, 07 Feb 2016 19:30:05 +0100
parents 79c5a86fcdfe
children c4cbb5689388
files runtime/doc/channel.txt runtime/doc/eval.txt src/channel.c src/eval.c src/json.c src/json_test.c src/proto/channel.pro src/proto/json.pro src/structs.h src/testdir/test_json.vim src/version.c src/vim.h
diffstat 12 files changed, 379 insertions(+), 138 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/channel.txt
+++ b/runtime/doc/channel.txt
@@ -1,4 +1,4 @@
-*channel.txt*      For Vim version 7.4.  Last change: 2016 Feb 06
+*channel.txt*      For Vim version 7.4.  Last change: 2016 Feb 07
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -16,7 +16,7 @@ The Netbeans interface also uses a chann
 
 1. Demo					|channel-demo|
 2. Opening a channel			|channel-open|
-3. Using a JSON channel			|channel-use|
+3. Using a JSON or JS channel		|channel-use|
 4. Vim commands				|channel-commands|
 5. Using a raw channel			|channel-use|
 6. Job control				|job-control|
@@ -77,6 +77,7 @@ To open a channel: >
 
 "mode" can be:						*channel-mode*
 	"json" - Use JSON, see below; most convenient way. Default.
+	"js"   - Use JavaScript encoding, more efficient than JSON.
 	"raw"  - Use raw messages
 
 							*channel-callback*
@@ -86,7 +87,7 @@ message. Example: >
 	func Handle(handle, msg)
 	  echo 'Received: ' . a:msg
 	endfunc
-	let handle = ch_open("localhost:8765", 'json', "Handle")
+	let handle = ch_open("localhost:8765", {"callback": "Handle"})
 
 "waittime" is the time to wait for the connection to be made in milliseconds.
 The default is zero, don't wait, which is useful if the server is supposed to
@@ -95,12 +96,12 @@ be running already.  A negative number w
 "timeout" is the time to wait for a request when blocking, using
 ch_sendexpr().  Again in milliseconds.  The default is 2000 (2 seconds).
 
-When "mode" is "json" the "msg" argument is the body of the received message,
-converted to Vim types.
+When "mode" is "json" or "js" the "msg" argument is the body of the received
+message, converted to Vim types.
 When "mode" is "raw" the "msg" argument is the whole message as a string.
 
-When "mode" is "json" the "callback" is optional.  When omitted it is only
-possible to receive a message after sending one.
+When "mode" is "json" or "js" the "callback" is optional.  When omitted it is
+only possible to receive a message after sending one.
 
 The handler can be added or changed later: >
     call ch_setcallback(handle, {callback})
@@ -123,12 +124,15 @@ If there is an error reading or writing 
 *E896* *E630* *E631* 
 
 ==============================================================================
-3. Using a JSON channel					*channel-use*
+3. Using a JSON or JS channel					*channel-use*
 
 If {mode} is "json" then a message can be sent synchronously like this: >
     let response = ch_sendexpr(handle, {expr})
 This awaits a response from the other side.
 
+When {mode} is "js" this works the same, except that the messages use
+JavaScript encoding.  See |jsencode()| for the difference.
+
 To send a message, without handling a response: >
     call ch_sendexpr(handle, {expr}, 0)
 
@@ -231,7 +235,8 @@ Here {number} is the same as what was in
 to avoid confusion with message that Vim sends.
 
 {result} is the result of the evaluation and is JSON encoded.  If the
-evaluation fails it is the string "ERROR".
+evaluation fails or the result can't be encoded in JSON it is the string
+"ERROR".
 
 
 Command "expr" ~
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -1956,6 +1956,8 @@ job_start({command} [, {options}]) Job	s
 job_status({job})		String	get the status of a job
 job_stop({job} [, {how}])	Number	stop a job
 join( {list} [, {sep}])		String	join {list} items into one String
+jsdecode( {string})		any	decode JS style JSON
+jsencode( {expr})		String	encode JS style JSON
 jsondecode( {string})		any	decode JSON
 jsonencode( {expr})		String	encode JSON
 keys( {dict})			List	keys in {dict}
@@ -2439,7 +2441,6 @@ bufwinnr({expr})					*bufwinnr()*
 		|:wincmd|.
 		Only deals with the current tab page.
 
-
 byte2line({byte})					*byte2line()*
 		Return the line number that contains the character at byte
 		count {byte} in the current buffer.  This includes the
@@ -2688,7 +2689,7 @@ ch_open({address} [, {argdict}])				*ch_
 
 		If {argdict} is given it must be a |Dictionary|.  The optional
 		items are:
-			mode        "raw" or "json".
+			mode        "raw", "js" or "json".
 				    Default "json".
 			callback    function to call for requests with a zero
 				    sequence number.  See |channel-callback|.
@@ -4381,17 +4382,33 @@ join({list} [, {sep}])					*join()*
 		converted into a string like with |string()|.
 		The opposite function is |split()|.
 
+jsdecode({string})					*jsdecode()*
+		This is similar to |jsondecode()| with these differences:
+		- Object key names do not have to be in quotes.
+		- Empty items in an array (between two commas) are allowed and
+		  result in v:none items.
+
+jsencode({expr})					*jsencode()*
+		This is similar to |jsonencode()| with these differences:
+		- Object key names are not in quotes.
+		- v:none items in an array result in an empty item between
+		  commas.
+		For example, the Vim object:
+			[1,v:none,{"one":1}],v:none ~
+		Will be encoded as:
+			[1,,{one:1},,] ~
+		While jsonencode() would produce:
+			[1,null,{"one":1},null] ~
+		This encoding is valid for JavaScript. It is more efficient
+		than JSON, especially when using an array with optional items.
+
+
 jsondecode({string})					*jsondecode()*
 		This parses a JSON formatted string and returns the equivalent
 		in Vim values.  See |jsonencode()| for the relation between
 		JSON and Vim values.
 		The decoding is permissive:
 		- A trailing comma in an array and object is ignored.
-		- An empty item in an array, two commas with nothing or white
-		  space in between, results in v:none.
-		- When an object member name is not a string it is converted
-		  to a string.  E.g. the number 123 is used as the string
-		  "123".
 		- More floating point numbers are recognized, e.g. "1." for
 		  "1.0".
 		The result must be a valid Vim type:
@@ -4413,7 +4430,7 @@ jsonencode({expr})					*jsonencode()*
 		   			used recursively: {}
 		   v:false		"false"
 		   v:true		"true"
-		   v:none		nothing
+		   v:none		"null"
 		   v:null		"null"
 		Note that using v:none is permitted, although the JSON
 		standard does not allow empty items.  This can be useful for
--- a/src/channel.c
+++ b/src/channel.c
@@ -119,7 +119,7 @@ typedef struct {
     char_u    *ch_callback;	/* function to call when a msg is not handled */
     cbq_T     ch_cb_head;	/* dummy node for pre-request callbacks */
 
-    int	      ch_json_mode;	/* TRUE for a json channel */
+    ch_mode_T ch_mode;
     jsonq_T   ch_json_head;	/* dummy node, header for circular queue */
 
     int       ch_timeout;	/* request timeout in msec */
@@ -526,12 +526,12 @@ channel_open(char *hostname, int port_in
 }
 
 /*
- * Set the json mode of channel "idx" to TRUE or FALSE.
+ * Set the json mode of channel "idx" to "ch_mode".
  */
     void
-channel_set_json_mode(int idx, int json_mode)
+channel_set_json_mode(int idx, ch_mode_T ch_mode)
 {
-    channels[idx].ch_json_mode = json_mode;
+    channels[idx].ch_mode = ch_mode;
 }
 
 /*
@@ -672,7 +672,8 @@ channel_parse_json(int ch_idx)
     js_read_T	reader;
     typval_T	listtv;
     jsonq_T	*item;
-    jsonq_T	*head = &channels[ch_idx].ch_json_head;
+    channel_T	*channel = &channels[ch_idx];
+    jsonq_T	*head = &channel->ch_json_head;
     int		ret;
 
     if (channel_peek(ch_idx) == NULL)
@@ -685,7 +686,8 @@ channel_parse_json(int ch_idx)
     reader.js_fill = NULL;
     /* reader.js_fill = channel_fill; */
     reader.js_cookie = &ch_idx;
-    ret = json_decode(&reader, &listtv);
+    ret = json_decode(&reader, &listtv,
+				   channel->ch_mode == MODE_JS ? JSON_JS : 0);
     if (ret == OK)
     {
 	/* Only accept the response when it is a list with at least two
@@ -854,6 +856,8 @@ channel_exe_cmd(int idx, char_u *cmd, ty
 	    typval_T	*tv;
 	    typval_T	err_tv;
 	    char_u	*json = NULL;
+	    channel_T	*channel = &channels[idx];
+	    int		options = channel->ch_mode == MODE_JS ? JSON_JS : 0;
 
 	    /* Don't pollute the display with errors. */
 	    ++emsg_skip;
@@ -861,7 +865,8 @@ channel_exe_cmd(int idx, char_u *cmd, ty
 	    if (is_eval)
 	    {
 		if (tv != NULL)
-		    json = json_encode_nr_expr(arg3->vval.v_number, tv);
+		    json = json_encode_nr_expr(arg3->vval.v_number, tv,
+								     options);
 		if (tv == NULL || (json != NULL && *json == NUL))
 		{
 		    /* If evaluation failed or the result can't be encoded
@@ -869,7 +874,8 @@ channel_exe_cmd(int idx, char_u *cmd, ty
 		    err_tv.v_type = VAR_STRING;
 		    err_tv.vval.v_string = (char_u *)"ERROR";
 		    tv = &err_tv;
-		    json = json_encode_nr_expr(arg3->vval.v_number, tv);
+		    json = json_encode_nr_expr(arg3->vval.v_number, tv,
+								     options);
 		}
 		if (json != NULL)
 		{
@@ -900,13 +906,13 @@ may_invoke_callback(int idx)
     typval_T	argv[3];
     int		seq_nr = -1;
     channel_T	*channel = &channels[idx];
-    int		json_mode = channel->ch_json_mode;
+    ch_mode_T	ch_mode = channel->ch_mode;
 
     if (channel->ch_close_cb != NULL)
 	/* this channel is handled elsewhere (netbeans) */
 	return FALSE;
 
-    if (json_mode)
+    if (ch_mode != MODE_RAW)
     {
 	/* Get any json message in the queue. */
 	if (channel_get_json(idx, -1, &listtv) == FAIL)
--- a/src/eval.c
+++ b/src/eval.c
@@ -628,6 +628,8 @@ static void f_job_stop(typval_T *argvars
 static void f_job_status(typval_T *argvars, typval_T *rettv);
 #endif
 static void f_join(typval_T *argvars, typval_T *rettv);
+static void f_jsdecode(typval_T *argvars, typval_T *rettv);
+static void f_jsencode(typval_T *argvars, typval_T *rettv);
 static void f_jsondecode(typval_T *argvars, typval_T *rettv);
 static void f_jsonencode(typval_T *argvars, typval_T *rettv);
 static void f_keys(typval_T *argvars, typval_T *rettv);
@@ -8206,6 +8208,8 @@ static struct fst
     {"job_stop",	1, 1, f_job_stop},
 #endif
     {"join",		1, 2, f_join},
+    {"jsdecode",	1, 1, f_jsdecode},
+    {"jsencode",	1, 1, f_jsencode},
     {"jsondecode",	1, 1, f_jsondecode},
     {"jsonencode",	1, 1, f_jsonencode},
     {"keys",		1, 1, f_keys},
@@ -9829,7 +9833,7 @@ f_ch_open(typval_T *argvars, typval_T *r
     int		port;
     int		waittime = 0;
     int		timeout = 2000;
-    int		json_mode = TRUE;
+    ch_mode_T	ch_mode = MODE_JSON;
     int		ch_idx;
 
     /* default: fail */
@@ -9868,8 +9872,12 @@ f_ch_open(typval_T *argvars, typval_T *r
 	{
 	    mode = get_dict_string(dict, (char_u *)"mode", FALSE);
 	    if (STRCMP(mode, "raw") == 0)
-		json_mode = FALSE;
-	    else if (STRCMP(mode, "json") != 0)
+		ch_mode = MODE_RAW;
+	    else if (STRCMP(mode, "js") == 0)
+		ch_mode = MODE_JS;
+	    else if (STRCMP(mode, "json") == 0)
+		ch_mode = MODE_JSON;
+	    else
 	    {
 		EMSG2(_(e_invarg2), mode);
 		return;
@@ -9891,7 +9899,7 @@ f_ch_open(typval_T *argvars, typval_T *r
     ch_idx = channel_open((char *)address, port, waittime, NULL);
     if (ch_idx >= 0)
     {
-	channel_set_json_mode(ch_idx, json_mode);
+	channel_set_json_mode(ch_idx, ch_mode);
 	channel_set_timeout(ch_idx, timeout);
 	if (callback != NULL && *callback != NUL)
 	    channel_set_callback(ch_idx, callback);
@@ -9946,7 +9954,7 @@ f_ch_sendexpr(typval_T *argvars, typval_
     rettv->vval.v_string = NULL;
 
     id = channel_get_id();
-    text = json_encode_nr_expr(id, &argvars[1]);
+    text = json_encode_nr_expr(id, &argvars[1], 0);
     if (text == NULL)
 	return;
 
@@ -14443,6 +14451,31 @@ f_join(typval_T *argvars, typval_T *rett
 }
 
 /*
+ * "jsdecode()" function
+ */
+    static void
+f_jsdecode(typval_T *argvars, typval_T *rettv)
+{
+    js_read_T	reader;
+
+    reader.js_buf = get_tv_string(&argvars[0]);
+    reader.js_fill = NULL;
+    reader.js_used = 0;
+    if (json_decode_all(&reader, rettv, JSON_JS) != OK)
+	EMSG(_(e_invarg));
+}
+
+/*
+ * "jsencode()" function
+ */
+    static void
+f_jsencode(typval_T *argvars, typval_T *rettv)
+{
+    rettv->v_type = VAR_STRING;
+    rettv->vval.v_string = json_encode(&argvars[0], JSON_JS);
+}
+
+/*
  * "jsondecode()" function
  */
     static void
@@ -14453,7 +14486,7 @@ f_jsondecode(typval_T *argvars, typval_T
     reader.js_buf = get_tv_string(&argvars[0]);
     reader.js_fill = NULL;
     reader.js_used = 0;
-    if (json_decode_all(&reader, rettv) != OK)
+    if (json_decode_all(&reader, rettv, 0) != OK)
 	EMSG(_(e_invarg));
 }
 
@@ -14464,7 +14497,7 @@ f_jsondecode(typval_T *argvars, typval_T
 f_jsonencode(typval_T *argvars, typval_T *rettv)
 {
     rettv->v_type = VAR_STRING;
-    rettv->vval.v_string = json_encode(&argvars[0]);
+    rettv->vval.v_string = json_encode(&argvars[0], 0);
 }
 
 /*
--- 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 *va
 	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
 	    {
 		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
 		    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
 				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, typ
 	    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, typ
 }
 
     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, ty
 	    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] != '"')
+	{
+	    /* 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
 	{
-	    key = get_tv_string_buf_chk(&tvkey, buf);
-	    if (key == NULL || *key == NUL)
+	    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, ty
 	++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, ty
  * 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, typv
     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, typv
 
 /*
  * 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, typva
 
 /*
  * 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 
 
 /*
  * 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 
  * 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;
 }
--- a/src/json_test.c
+++ b/src/json_test.c
@@ -35,107 +35,107 @@ test_decode_find_end(void)
 
     /* string and incomplete string */
     reader.js_buf = (char_u *)"\"hello\"";
-    assert(json_find_end(&reader) == OK);
+    assert(json_find_end(&reader, 0) == OK);
     reader.js_buf = (char_u *)"  \"hello\" ";
-    assert(json_find_end(&reader) == OK);
+    assert(json_find_end(&reader, 0) == OK);
     reader.js_buf = (char_u *)"\"hello";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
 
     /* number and dash (incomplete number) */
     reader.js_buf = (char_u *)"123";
-    assert(json_find_end(&reader) == OK);
+    assert(json_find_end(&reader, 0) == OK);
     reader.js_buf = (char_u *)"-";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
 
     /* false, true and null, also incomplete */
     reader.js_buf = (char_u *)"false";
-    assert(json_find_end(&reader) == OK);
+    assert(json_find_end(&reader, 0) == OK);
     reader.js_buf = (char_u *)"f";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"fa";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"fal";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"fals";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
 
     reader.js_buf = (char_u *)"true";
-    assert(json_find_end(&reader) == OK);
+    assert(json_find_end(&reader, 0) == OK);
     reader.js_buf = (char_u *)"t";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"tr";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"tru";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
 
     reader.js_buf = (char_u *)"null";
-    assert(json_find_end(&reader) == OK);
+    assert(json_find_end(&reader, 0) == OK);
     reader.js_buf = (char_u *)"n";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"nu";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"nul";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
 
     /* object without white space */
     reader.js_buf = (char_u *)"{\"a\":123}";
-    assert(json_find_end(&reader) == OK);
+    assert(json_find_end(&reader, 0) == OK);
     reader.js_buf = (char_u *)"{\"a\":123";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"{\"a\":";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"{\"a\"";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"{\"a";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"{\"";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"{";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
 
     /* object with white space */
     reader.js_buf = (char_u *)"  {  \"a\"  :  123  }  ";
-    assert(json_find_end(&reader) == OK);
+    assert(json_find_end(&reader, 0) == OK);
     reader.js_buf = (char_u *)"  {  \"a\"  :  123  ";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"  {  \"a\"  :  ";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"  {  \"a\"  ";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"  {  \"a  ";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"  {   ";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
 
     /* array without white space */
     reader.js_buf = (char_u *)"[\"a\",123]";
-    assert(json_find_end(&reader) == OK);
+    assert(json_find_end(&reader, 0) == OK);
     reader.js_buf = (char_u *)"[\"a\",123";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"[\"a\",";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"[\"a\"";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"[\"a";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"[\"";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"[";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
 
     /* array with white space */
     reader.js_buf = (char_u *)"  [  \"a\"  ,  123  ]  ";
-    assert(json_find_end(&reader) == OK);
+    assert(json_find_end(&reader, 0) == OK);
     reader.js_buf = (char_u *)"  [  \"a\"  ,  123  ";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"  [  \"a\"  ,  ";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"  [  \"a\"  ";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"  [  \"a  ";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
     reader.js_buf = (char_u *)"  [  ";
-    assert(json_find_end(&reader) == MAYBE);
+    assert(json_find_end(&reader, 0) == MAYBE);
 }
 
     static int
@@ -157,15 +157,15 @@ test_fill_called_on_find_end(void)
     reader.js_used = 0;
     reader.js_buf = (char_u *)"  [  \"a\"  ,  123  ";
     reader.js_cookie =        "  [  \"a\"  ,  123  ]  ";
-    assert(json_find_end(&reader) == OK);
+    assert(json_find_end(&reader, 0) == OK);
     reader.js_buf = (char_u *)"  [  \"a\"  ,  ";
-    assert(json_find_end(&reader) == OK);
+    assert(json_find_end(&reader, 0) == OK);
     reader.js_buf = (char_u *)"  [  \"a\"  ";
-    assert(json_find_end(&reader) == OK);
+    assert(json_find_end(&reader, 0) == OK);
     reader.js_buf = (char_u *)"  [  \"a";
-    assert(json_find_end(&reader) == OK);
+    assert(json_find_end(&reader, 0) == OK);
     reader.js_buf = (char_u *)"  [  ";
-    assert(json_find_end(&reader) == OK);
+    assert(json_find_end(&reader, 0) == OK);
 }
 
 /*
--- a/src/proto/channel.pro
+++ b/src/proto/channel.pro
@@ -1,7 +1,7 @@
 /* channel.c */
 void channel_gui_register_all(void);
 int channel_open(char *hostname, int port_in, int waittime, void (*close_cb)(void));
-void channel_set_json_mode(int idx, int json_mode);
+void channel_set_json_mode(int idx, ch_mode_T ch_mode);
 void channel_set_timeout(int idx, int timeout);
 void channel_set_callback(int idx, char_u *callback);
 void channel_set_req_callback(int idx, char_u *callback, int id);
--- a/src/proto/json.pro
+++ b/src/proto/json.pro
@@ -1,7 +1,7 @@
 /* json.c */
-char_u *json_encode(typval_T *val);
-char_u *json_encode_nr_expr(int nr, typval_T *val);
-int json_decode_all(js_read_T *reader, typval_T *res);
-int json_decode(js_read_T *reader, typval_T *res);
-int json_find_end(js_read_T *reader);
+char_u *json_encode(typval_T *val, int options);
+char_u *json_encode_nr_expr(int nr, typval_T *val, int options);
+int json_decode_all(js_read_T *reader, typval_T *res, int options);
+int json_decode(js_read_T *reader, typval_T *res, int options);
+int json_find_end(js_read_T *reader, int options);
 /* vim: set ft=c : */
--- a/src/structs.h
+++ b/src/structs.h
@@ -2728,3 +2728,11 @@ struct js_reader
     void	*js_cookie;	/* can be used by js_fill */
 };
 typedef struct js_reader js_read_T;
+
+/* mode for a channel */
+typedef enum
+{
+    MODE_RAW = 0,
+    MODE_JSON,
+    MODE_JS
+} ch_mode_T;
--- a/src/testdir/test_json.vim
+++ b/src/testdir/test_json.vim
@@ -32,9 +32,12 @@ let l3 = [1, 2]
 let s:varl3 = [l3, l3]
 
 let s:jsond1 = '{"a":1,"b":"bee","c":[1,2]}'
+let s:jsd1 = '{a:1,b:"bee",c:[1,2]}'
 let s:vard1 = {"a": 1, "b": "bee","c": [1,2]}
 let s:jsond2 = '{"1":1,"2":{"a":"aa","b":{},"c":"cc"},"3":3}'
+let s:jsd2 = '{"1":1,"2":{a:"aa",b:{},c:"cc"},"3":3}'
 let s:jsond2s = "  { \"1\" : 1 , \"2\" :\n{ \"a\"\r: \"aa\" , \"b\" : {\<Tab>} , \"c\" : \"cc\" } , \"3\" : 3 }\r\n"
+let s:jsd2s = "  { \"1\" : 1 , \"2\" :\n{ a\r: \"aa\" , b : {\<Tab>} , c : \"cc\" } , \"3\" : 3 }\r\n"
 let s:vard2 = {"1": 1, "2": 2, "3": 3}
 let d2 = {"a": "aa", "b": s:vard2, "c": "cc"}
 let s:vard2["2"] = d2
@@ -42,11 +45,16 @@ let s:vard2x = {"1": 1, "2": {"a": "aa",
 let d3 = {"a": 1, "b": 2}
 let s:vard3 = {"x": d3, "y": d3}
 let s:jsond3 = '{"x":{"a":1,"b":2},"y":{"a":1,"b":2}}'
+let s:jsd3 = '{x:{a:1,b:2},y:{a:1,b:2}}'
+let s:vard4 = {"key": v:none}
+let s:vard4x = {"key": v:null}
+let s:jsond4 = '{"key":null}'
+let s:jsd4 = '{key:null}'
 
-let s:jsonvals = '[true,false,,null]'
-let s:varvals = [v:true, v:false, v:none, v:null]
+let s:jsonvals = '[true,false,null,null]'
+let s:varvals = [v:true, v:false, v:null, v:null]
 
-func Test_encode()
+func Test_json_encode()
   call assert_equal(s:json1, jsonencode(s:var1))
   call assert_equal(s:json2, jsonencode(s:var2))
   call assert_equal(s:json3, jsonencode(s:var3))
@@ -69,18 +77,18 @@ func Test_encode()
   call assert_equal(s:jsond1, jsonencode(s:vard1))
   call assert_equal(s:jsond2, jsonencode(s:vard2))
   call assert_equal(s:jsond3, jsonencode(s:vard3))
+  call assert_equal(s:jsond4, jsonencode(s:vard4))
 
   call assert_equal(s:jsonvals, jsonencode(s:varvals))
 
   call assert_fails('echo jsonencode(function("tr"))', 'E474:')
   call assert_fails('echo jsonencode([function("tr")])', 'E474:')
-  call assert_fails('echo jsonencode({"key":v:none})', 'E474:')
 
   silent! let res = jsonencode(function("tr"))
   call assert_equal("", res)
 endfunc
 
-func Test_decode()
+func Test_json_decode()
   call assert_equal(s:var1, jsondecode(s:json1))
   call assert_equal(s:var2, jsondecode(s:json2))
   call assert_equal(s:var3, jsondecode(s:json3))
@@ -103,7 +111,9 @@ func Test_decode()
 
   call assert_equal(s:vard1, jsondecode(s:jsond1))
   call assert_equal(s:vard2x, jsondecode(s:jsond2))
+  call assert_equal(s:vard2x, jsondecode(s:jsond2s))
   call assert_equal(s:vard3, jsondecode(s:jsond3))
+  call assert_equal(s:vard4x, jsondecode(s:jsond4))
 
   call assert_equal(s:varvals, jsondecode(s:jsonvals))
 
@@ -134,4 +144,110 @@ func Test_decode()
   call assert_fails('call jsondecode("[1")', "E474:")
   call assert_fails('call jsondecode("[1,")', "E474:")
   call assert_fails('call jsondecode("[1 2]")', "E474:")
+
+  call assert_fails('call jsondecode("[1,,2]")', "E474:")
 endfunc
+
+let s:jsl5 = '[7,,,]'
+let s:varl5 = [7, v:none, v:none]
+
+func Test_js_encode()
+  call assert_equal(s:json1, jsencode(s:var1))
+  call assert_equal(s:json2, jsencode(s:var2))
+  call assert_equal(s:json3, jsencode(s:var3))
+  call assert_equal(s:json4, jsencode(s:var4))
+  call assert_equal(s:json5, jsencode(s:var5))
+
+  if has('multi_byte')
+    call assert_equal(s:jsonmb, jsencode(s:varmb))
+  endif
+
+  call assert_equal(s:jsonnr, jsencode(s:varnr))
+  if has('float')
+    call assert_equal(s:jsonfl, jsencode(s:varfl))
+  endif
+
+  call assert_equal(s:jsonl1, jsencode(s:varl1))
+  call assert_equal(s:jsonl2, jsencode(s:varl2))
+  call assert_equal(s:jsonl3, jsencode(s:varl3))
+
+  call assert_equal(s:jsd1, jsencode(s:vard1))
+  call assert_equal(s:jsd2, jsencode(s:vard2))
+  call assert_equal(s:jsd3, jsencode(s:vard3))
+  call assert_equal(s:jsd4, jsencode(s:vard4))
+
+  call assert_equal(s:jsonvals, jsencode(s:varvals))
+
+  call assert_fails('echo jsencode(function("tr"))', 'E474:')
+  call assert_fails('echo jsencode([function("tr")])', 'E474:')
+
+  silent! let res = jsencode(function("tr"))
+  call assert_equal("", res)
+
+  call assert_equal(s:jsl5, jsencode(s:varl5))
+endfunc
+
+func Test_js_decode()
+  call assert_equal(s:var1, jsdecode(s:json1))
+  call assert_equal(s:var2, jsdecode(s:json2))
+  call assert_equal(s:var3, jsdecode(s:json3))
+  call assert_equal(s:var4, jsdecode(s:json4))
+  call assert_equal(s:var5, jsdecode(s:json5))
+
+  if has('multi_byte')
+    call assert_equal(s:varmb, jsdecode(s:jsonmb))
+  endif
+
+  call assert_equal(s:varnr, jsdecode(s:jsonnr))
+  if has('float')
+    call assert_equal(s:varfl, jsdecode(s:jsonfl))
+  endif
+
+  call assert_equal(s:varl1, jsdecode(s:jsonl1))
+  call assert_equal(s:varl2x, jsdecode(s:jsonl2))
+  call assert_equal(s:varl2x, jsdecode(s:jsonl2s))
+  call assert_equal(s:varl3, jsdecode(s:jsonl3))
+
+  call assert_equal(s:vard1, jsdecode(s:jsond1))
+  call assert_equal(s:vard1, jsdecode(s:jsd1))
+  call assert_equal(s:vard2x, jsdecode(s:jsond2))
+  call assert_equal(s:vard2x, jsdecode(s:jsd2))
+  call assert_equal(s:vard2x, jsdecode(s:jsond2s))
+  call assert_equal(s:vard2x, jsdecode(s:jsd2s))
+  call assert_equal(s:vard3, jsdecode(s:jsond3))
+  call assert_equal(s:vard3, jsdecode(s:jsd3))
+  call assert_equal(s:vard4x, jsdecode(s:jsond4))
+  call assert_equal(s:vard4x, jsdecode(s:jsd4))
+
+  call assert_equal(s:varvals, jsdecode(s:jsonvals))
+
+  call assert_equal(v:true, jsdecode('true'))
+  call assert_equal(type(v:true), type(jsdecode('true')))
+  call assert_equal(v:none, jsdecode(''))
+  call assert_equal(type(v:none), type(jsdecode('')))
+  call assert_equal("", jsdecode('""'))
+
+  call assert_equal({'n': 1}, jsdecode('{"n":1,}'))
+
+  call assert_fails('call jsdecode("\"")', "E474:")
+  call assert_fails('call jsdecode("blah")', "E474:")
+  call assert_fails('call jsdecode("true blah")', "E474:")
+  call assert_fails('call jsdecode("<foobar>")', "E474:")
+
+  call assert_fails('call jsdecode("{")', "E474:")
+  call assert_fails('call jsdecode("{foobar}")', "E474:")
+  call assert_fails('call jsdecode("{\"n\",")', "E474:")
+  call assert_fails('call jsdecode("{\"n\":")', "E474:")
+  call assert_fails('call jsdecode("{\"n\":1")', "E474:")
+  call assert_fails('call jsdecode("{\"n\":1,")', "E474:")
+  call assert_fails('call jsdecode("{\"n\",1}")', "E474:")
+  call assert_fails('call jsdecode("{-}")', "E474:")
+
+  call assert_fails('call jsdecode("[foobar]")', "E474:")
+  call assert_fails('call jsdecode("[")', "E474:")
+  call assert_fails('call jsdecode("[1")', "E474:")
+  call assert_fails('call jsdecode("[1,")', "E474:")
+  call assert_fails('call jsdecode("[1 2]")', "E474:")
+
+  call assert_equal(s:varl5, jsdecode(s:jsl5))
+endfunc
--- a/src/version.c
+++ b/src/version.c
@@ -748,6 +748,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1279,
+/**/
     1278,
 /**/
     1277,
--- a/src/vim.h
+++ b/src/vim.h
@@ -2317,6 +2317,10 @@ typedef int sock_T;
 # define MAX_OPEN_CHANNELS 0
 #endif
 
+/* Options for json_encode() and json_decode. */
+#define JSON_JS		1   /* use JS instead of JSON */
+#define JSON_NO_NONE	2   /* v:none item not allowed */
+
 #ifdef FEAT_MZSCHEME
 /* this is in main.c, cproto can't handle it. */
 int vim_main2(int argc, char **argv);