-*builtin.txt* For Vim version 9.2. Last change: 2026 Mar 17
+*builtin.txt* For Vim version 9.2. Last change: 2026 Mar 25
VIM REFERENCE MANUAL by Bram Moolenaar
- Strings can be in single quotes.
- Empty items in an array (between two commas) are allowed and
result in v:none items.
+ - Capitalization is ignored in keywords: true, false, null,
+ NaN, Infinity and -Infinity.
Can also be used as a |method|: >
ReadObject()->js_decode()
same as {"1":2}.
- More floating point numbers are recognized, e.g. "1." for
"1.0", or "001.2" for "1.2". Special floating point values
- "Infinity", "-Infinity" and "NaN" (capitalization ignored)
- are accepted.
+ "Infinity", "-Infinity" and "NaN" are accepted.
- Leading zeroes in integer numbers are ignored, e.g. "012"
for "12" or "-012" for "-12".
- - Capitalization is ignored in literal names null, true or
- false, e.g. "NULL" for "null", "True" for "true".
- Control characters U+0000 through U+001F which are not
escaped in strings are accepted, e.g. " " (tab
character in string) for "\t".
and results in v:none.
- Backslash in an invalid 2-character sequence escape is
ignored, e.g. "\a" is decoded as "a".
- - A correct surrogate pair in JSON strings should normally be
- a 12 character sequence such as "\uD834\uDD1E", but
- json_decode() silently accepts truncated surrogate pairs
- such as "\uD834" or "\uD834\u"
+ - A surrogate pair in JSON strings is a 12 character sequence
+ such as "\uD834\uDD1E". A lone surrogate or an invalid
+ surrogate pair (e.g. "\uD800" or "\uD800\uD800") results
+ in an error.
*E938*
A duplicate key in an object, valid in rfc7159, is not
accepted by json_decode() as the result must be a valid Vim
-*version9.txt* For Vim version 9.2. Last change: 2026 Mar 22
+*version9.txt* For Vim version 9.2. Last change: 2026 Mar 25
VIM REFERENCE MANUAL by Bram Moolenaar
Changed~
-------
- Support for NeXTStep was dropped with patch v9.2.0122
+- |json_decode()| is stricter: keywords must be lowercase, lone surrogates are
+ now invalid
+- |js_decode()| rejects lone surrogates
*added-9.3*
Added ~
}
#endif
ga_append(gap, '"');
+ // Pre-grow for the common case: input length + quotes + some escapes.
+ ga_grow(gap, (int)STRLEN(res) + 2);
// `from` is the beginning of a sequence of bytes we can directly copy from
// the input string, avoiding the overhead associated to decoding/encoding
// them.
switch (c)
{
case 0x08:
- ga_append(gap, '\\'); ga_append(gap, 'b'); break;
+ GA_CONCAT_LITERAL(gap, "\\b"); break;
case 0x09:
- ga_append(gap, '\\'); ga_append(gap, 't'); break;
+ GA_CONCAT_LITERAL(gap, "\\t"); break;
case 0x0a:
- ga_append(gap, '\\'); ga_append(gap, 'n'); break;
+ GA_CONCAT_LITERAL(gap, "\\n"); break;
case 0x0c:
- ga_append(gap, '\\'); ga_append(gap, 'f'); break;
+ GA_CONCAT_LITERAL(gap, "\\f"); break;
case 0x0d:
- ga_append(gap, '\\'); ga_append(gap, 'r'); break;
+ GA_CONCAT_LITERAL(gap, "\\r"); break;
case 0x22: // "
+ GA_CONCAT_LITERAL(gap, "\\\""); break;
case 0x5c: // backslash
- ga_append(gap, '\\');
- ga_append(gap, c);
- break;
+ GA_CONCAT_LITERAL(gap, "\\\\"); break;
default:
{
size_t numbuflen;
ga_append(gap, '[');
for (i = 0; i < b->bv_ga.ga_len; i++)
{
- size_t numbuflen;
+ int byte = blob_get(b, i);
if (i > 0)
- GA_CONCAT_LITERAL(gap, ",");
- numbuflen = vim_snprintf_safelen((char *)numbuf, sizeof(numbuf),
- "%d", blob_get(b, i));
- ga_concat_len(gap, numbuf, numbuflen);
+ ga_append(gap, ',');
+ // blob bytes are 0-255, use simple conversion
+ if (byte >= 100)
+ {
+ ga_append(gap, '0' + byte / 100);
+ ga_append(gap, '0' + (byte / 10) % 10);
+ ga_append(gap, '0' + byte % 10);
+ }
+ else if (byte >= 10)
+ {
+ ga_append(gap, '0' + byte / 10);
+ ga_append(gap, '0' + byte % 10);
+ }
+ else
+ ga_append(gap, '0' + byte);
}
ga_append(gap, ']');
}
return FAIL;
}
p += len + 2;
- if (0xd800 <= nr && nr <= 0xdfff
+ if (0xd800 <= nr && nr <= 0xdbff
&& (int)(reader->js_end - p) >= 6
&& *p == '\\' && *(p+1) == 'u')
{
((nr2 - 0xdc00) & 0x3ff)) + 0x10000;
}
}
+ // Lone surrogate is invalid.
+ if (0xd800 <= nr && nr <= 0xdfff)
+ {
+ if (res != NULL)
+ ga_clear(&ga);
+ return FAIL;
+ }
if (res != NULL)
{
char_u buf[NUMBUFLEN];
retval = OK;
break;
}
- if (STRNICMP((char *)p, "false", 5) == 0)
+ // In strinct JSON mode, keywords must be lowercase.
+ // In JS mode, keywords are case-insensitive.
+#define MATCH_KW(p, kw, len) \
+ ((options & JSON_JS) \
+ ? STRNICMP((char *)(p), (kw), (len)) == 0 \
+ : STRNCMP((char *)(p), (kw), (len)) == 0)
+ if (MATCH_KW(p, "false", 5))
{
reader->js_used += 5;
if (cur_item != NULL)
retval = OK;
break;
}
- if (STRNICMP((char *)p, "true", 4) == 0)
+ if (MATCH_KW(p, "true", 4))
{
reader->js_used += 4;
if (cur_item != NULL)
retval = OK;
break;
}
- if (STRNICMP((char *)p, "null", 4) == 0)
+ if (MATCH_KW(p, "null", 4))
{
reader->js_used += 4;
if (cur_item != NULL)
retval = OK;
break;
}
- if (STRNICMP((char *)p, "NaN", 3) == 0)
+ if (MATCH_KW(p, "NaN", 3))
{
reader->js_used += 3;
if (cur_item != NULL)
retval = OK;
break;
}
- if (STRNICMP((char *)p, "-Infinity", 9) == 0)
+ if (MATCH_KW(p, "-Infinity", 9))
{
reader->js_used += 9;
if (cur_item != NULL)
retval = OK;
break;
}
- if (STRNICMP((char *)p, "Infinity", 8) == 0)
+ if (MATCH_KW(p, "Infinity", 8))
{
reader->js_used += 8;
if (cur_item != NULL)
retval = OK;
break;
}
+#undef MATCH_KW
// check for truncated name
len = (int)(reader->js_end
- (reader->js_buf + reader->js_used));
" surrogate pair
let s:jsonsp1 = '"\ud83c\udf63"'
let s:varsp1 = "\xf0\x9f\x8d\xa3"
+" high surrogate followed by non-surrogate is invalid (lone surrogate)
let s:jsonsp2 = '"\ud83c\u00a0"'
-let s:varsp2 = "\ud83c\u00a0"
let s:jsonmb = '"s¢cĴgё"'
let s:varmb = "s¢cĴgё"
call assert_equal(s:varmb, json_decode(s:jsonmb))
call assert_equal(s:varsp1, json_decode(s:jsonsp1))
- call assert_equal(s:varsp2, json_decode(s:jsonsp2))
+ call assert_fails('call json_decode(s:jsonsp2)', 'E491:')
call assert_equal(s:varnr, json_decode(s:jsonnr))
call assert_equal(s:varfl, json_decode(s:jsonfl))
call assert_equal(type(v:none), type(json_decode('')))
call assert_equal("", json_decode('""'))
+ " json_decode() requires lowercase keywords (RFC 7159)
+ call assert_fails('call json_decode("True")', 'E491:')
+ call assert_fails('call json_decode("FALSE")', 'E491:')
+ call assert_fails('call json_decode("Null")', 'E491:')
+ call assert_fails('call json_decode("NULL")', 'E491:')
+ call assert_fails('call json_decode("nan")', 'E491:')
+ call assert_fails('call json_decode("NAN")', 'E491:')
+ call assert_fails('call json_decode("infinity")', 'E491:')
+ call assert_fails('call json_decode("INFINITY")', 'E491:')
+ call assert_fails('call json_decode("-infinity")', 'E491:')
+ call assert_fails('call json_decode("-INFINITY")', 'E491:')
+
" Character in string after \ is ignored if not special.
call assert_equal("x", json_decode('"\x"'))
" but not twice
call assert_fails("call json_decode('{\"\": \"ok\", \"\": \"bad\"}')", 'E938:')
+ " lone surrogate is invalid
+ call assert_fails('call json_decode("\"\\uD800\"")', 'E491:')
+ call assert_fails('call json_decode("\"\\uDC00\"")', 'E491:')
+ call assert_fails('call json_decode("\"\\uD800\\uD800\"")', 'E491:')
+ call assert_fails('call json_decode("\"\\uDC00\\uDC00\"")', 'E491:')
+
call assert_equal({'n': 1}, json_decode('{"n":1,}'))
call assert_fails("call json_decode(\"{'n':'1',}\")", 'E491:')
call assert_fails("call json_decode(\"'n'\")", 'E491:')
call assert_equal(s:varmb, js_decode(s:jsonmb))
call assert_equal(s:varsp1, js_decode(s:jsonsp1))
- call assert_equal(s:varsp2, js_decode(s:jsonsp2))
+ call assert_fails('call js_decode(s:jsonsp2)', 'E491:')
call assert_equal(s:varnr, js_decode(s:jsonnr))
call assert_equal(s:varfl, js_decode(s:jsonfl))
call assert_equal("", js_decode("''"))
call assert_equal('n', js_decode("'n'"))
+
+ " js_decode() accepts keywords case-insensitively
+ call assert_equal(v:true, js_decode('True'))
+ call assert_equal(v:true, js_decode('TRUE'))
+ call assert_equal(v:false, js_decode('False'))
+ call assert_equal(v:false, js_decode('FALSE'))
+ call assert_equal(v:null, js_decode('Null'))
+ call assert_equal(v:null, js_decode('NULL'))
+ call assert_true(isnan(js_decode('nan')))
+ call assert_equal(s:varposinf, js_decode('infinity'))
+ call assert_equal(s:varneginf, js_decode('-infinity'))
+ call assert_equal(s:varposinf, js_decode('INFINITY'))
+ call assert_equal(s:varneginf, js_decode('-INFINITY'))
+
call assert_equal({'n': 1}, js_decode('{"n":1,}'))
call assert_equal({'n': '1'}, js_decode("{'n':'1',}"))
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 248,
/**/
247,
/**/