aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEmanuel <emanuel@empa.xyz>2023-12-06 16:56:04 +0100
committerGitHub <noreply@github.com>2023-12-06 07:56:04 -0800
commite057b38e7037808b3593fb1035794595b4e4a45e (patch)
treec746a875319d527094eafd2143b0d89296250463
parentc84af395e88ba143c19f7e34674bd222622e08ee (diff)
downloadrneovim-e057b38e7037808b3593fb1035794595b4e4a45e.tar.gz
rneovim-e057b38e7037808b3593fb1035794595b4e4a45e.tar.bz2
rneovim-e057b38e7037808b3593fb1035794595b4e4a45e.zip
fix(json): allow objects with empty keys #25564
Problem: Empty string is a valid JSON key, but json_decode() treats an object with empty key as ":help msgpack-special-dict". #20757 :echo json_decode('{"": "1"}') {'_TYPE': [], '_VAL': [['', '1']]} Note: vim returns `{'': '1'}`. Solution: Allow empty string as an object key. Note that we still (currently) disallow empty keys in object_to_vim() (since 7c01d5ff9286d262097484c680e3a4eab49e2911): https://github.com/neovim/neovim/blob/f64e4b43e1191ff30d902730f752875aa55682ce/src/nvim/api/private/converter.c#L333-L334 Fix #20757 Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
-rw-r--r--runtime/doc/builtin.txt4
-rw-r--r--runtime/lua/vim/_meta/vimfn.lua4
-rw-r--r--src/nvim/eval.lua4
-rw-r--r--src/nvim/eval/decode.c14
-rw-r--r--test/functional/lua/json_spec.lua4
-rw-r--r--test/functional/vimscript/json_functions_spec.lua15
6 files changed, 17 insertions, 28 deletions
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index 6ffb514487..4b0da06971 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -3873,8 +3873,7 @@ json_decode({expr}) *json_decode()*
Vim value. In the following cases it will output
|msgpack-special-dict|:
1. Dictionary contains duplicate key.
- 2. Dictionary contains empty key.
- 3. String contains NUL byte. Two special dictionaries: for
+ 2. String contains NUL byte. Two special dictionaries: for
dictionary and for string will be emitted in case string
with NUL byte was a dictionary key.
@@ -4954,7 +4953,6 @@ msgpackparse({data}) *msgpackparse()*
are binary strings).
2. String with NUL byte inside.
3. Duplicate key.
- 4. Empty key.
ext |List| with two values: first is a signed integer
representing extension type. Second is
|readfile()|-style list of strings.
diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua
index 05e5b2b871..7234b813b6 100644
--- a/runtime/lua/vim/_meta/vimfn.lua
+++ b/runtime/lua/vim/_meta/vimfn.lua
@@ -4678,8 +4678,7 @@ function vim.fn.join(list, sep) end
--- Vim value. In the following cases it will output
--- |msgpack-special-dict|:
--- 1. Dictionary contains duplicate key.
---- 2. Dictionary contains empty key.
---- 3. String contains NUL byte. Two special dictionaries: for
+--- 2. String contains NUL byte. Two special dictionaries: for
--- dictionary and for string will be emitted in case string
--- with NUL byte was a dictionary key.
---
@@ -5922,7 +5921,6 @@ function vim.fn.msgpackdump(list, type) end
--- are binary strings).
--- 2. String with NUL byte inside.
--- 3. Duplicate key.
---- 4. Empty key.
--- ext |List| with two values: first is a signed integer
--- representing extension type. Second is
--- |readfile()|-style list of strings.
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 59423808be..51cf7bb0ea 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -5735,8 +5735,7 @@ M.funcs = {
Vim value. In the following cases it will output
|msgpack-special-dict|:
1. Dictionary contains duplicate key.
- 2. Dictionary contains empty key.
- 3. String contains NUL byte. Two special dictionaries: for
+ 2. String contains NUL byte. Two special dictionaries: for
dictionary and for string will be emitted in case string
with NUL byte was a dictionary key.
@@ -7155,7 +7154,6 @@ M.funcs = {
are binary strings).
2. String with NUL byte inside.
3. Duplicate key.
- 4. Empty key.
ext |List| with two values: first is a signed integer
representing extension type. Second is
|readfile()|-style list of strings.
diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c
index a6407693d7..64b65b42a5 100644
--- a/src/nvim/eval/decode.c
+++ b/src/nvim/eval/decode.c
@@ -141,9 +141,7 @@ static inline int json_decoder_pop(ValuesStackItem obj, ValuesStack *const stack
ValuesStackItem key = kv_pop(*stack);
if (last_container.special_val == NULL) {
// These cases should have already been handled.
- assert(!(key.is_special_string
- || key.val.vval.v_string == NULL
- || *key.val.vval.v_string == NUL));
+ assert(!(key.is_special_string || key.val.vval.v_string == NULL));
dictitem_T *const obj_di = tv_dict_item_alloc(key.val.vval.v_string);
tv_clear(&key.val);
if (tv_dict_add(last_container.container.vval.v_dict, obj_di)
@@ -170,11 +168,10 @@ static inline int json_decoder_pop(ValuesStackItem obj, ValuesStack *const stack
tv_clear(&obj.val);
return FAIL;
}
- // Handle empty key and key represented as special dictionary
+ // Handle special dictionaries
if (last_container.special_val == NULL
&& (obj.is_special_string
|| obj.val.vval.v_string == NULL
- || *obj.val.vval.v_string == NUL
|| tv_dict_find(last_container.container.vval.v_dict, obj.val.vval.v_string, -1))) {
tv_clear(&obj.val);
@@ -404,13 +401,6 @@ static inline int parse_json_string(const char *const buf, const size_t buf_len,
semsg(_("E474: Expected string end: %.*s"), (int)buf_len, buf);
goto parse_json_string_fail;
}
- if (len == 0) {
- POP(((typval_T) {
- .v_type = VAR_STRING,
- .vval = { .v_string = NULL },
- }), false);
- goto parse_json_string_ret;
- }
char *str = xmalloc(len + 1);
int fst_in_pair = 0;
char *str_end = str;
diff --git a/test/functional/lua/json_spec.lua b/test/functional/lua/json_spec.lua
index 25fdb48eea..705bfcf2f9 100644
--- a/test/functional/lua/json_spec.lua
+++ b/test/functional/lua/json_spec.lua
@@ -101,6 +101,8 @@ describe('vim.json.decode()', function()
eq({['1']=2}, exec_lua([[return vim.json.decode('{"1": 2}')]]))
eq({['1']=2, ['3']={{['4']={['5']={{}, 1}}}}},
exec_lua([[return vim.json.decode('{"1": 2, "3": [{"4": {"5": [ [], 1]}}]}')]]))
+ -- Empty string is a valid key. #20757
+ eq({['']=42}, exec_lua([[return vim.json.decode('{"": 42}')]]))
end)
it('parses strings properly', function()
@@ -161,6 +163,8 @@ describe('vim.json.encode()', function()
it('dumps dictionaries', function()
eq('{}', exec_lua([[return vim.json.encode(vim.empty_dict())]]))
eq('{"d":[]}', exec_lua([[return vim.json.encode({d={}})]]))
+ -- Empty string is a valid key. #20757
+ eq('{"":42}', exec_lua([[return vim.json.encode({['']=42})]]))
end)
it('dumps vim.NIL', function()
diff --git a/test/functional/vimscript/json_functions_spec.lua b/test/functional/vimscript/json_functions_spec.lua
index a9dab8431c..53899085e0 100644
--- a/test/functional/vimscript/json_functions_spec.lua
+++ b/test/functional/vimscript/json_functions_spec.lua
@@ -467,19 +467,18 @@ describe('json_decode() function', function()
'[1, {"d": {"b": 3, "a": 1, "c": 4, "a": 2, "c": 4}}]')
sp_decode_eq({1, {a={}, d={_TYPE='map', _VAL={{'b', 3}, {'a', 1}, {'c', 4}, {'a', 2}, {'c', 4}}}}},
'[1, {"a": [], "d": {"b": 3, "a": 1, "c": 4, "a": 2, "c": 4}}]')
- end)
-
- it('parses dictionaries with empty keys to special maps', function()
- sp_decode_eq({_TYPE='map', _VAL={{'', 4}}},
- '{"": 4}')
- sp_decode_eq({_TYPE='map', _VAL={{'b', 3}, {'a', 1}, {'c', 4}, {'d', 2}, {'', 4}}},
- '{"b": 3, "a": 1, "c": 4, "d": 2, "": 4}')
sp_decode_eq({_TYPE='map', _VAL={{'', 3}, {'a', 1}, {'c', 4}, {'d', 2}, {'', 4}}},
'{"": 3, "a": 1, "c": 4, "d": 2, "": 4}')
sp_decode_eq({{_TYPE='map', _VAL={{'', 3}, {'a', 1}, {'c', 4}, {'d', 2}, {'', 4}}}},
'[{"": 3, "a": 1, "c": 4, "d": 2, "": 4}]')
end)
+ it('parses dictionaries with empty keys', function()
+ eq({[""] = 4}, funcs.json_decode('{"": 4}'))
+ eq({b = 3, a = 1, c = 4, d = 2, [""] = 4},
+ funcs.json_decode('{"b": 3, "a": 1, "c": 4, "d": 2, "": 4}'))
+ end)
+
it('parses dictionaries with keys with NUL bytes to special maps', function()
sp_decode_eq({_TYPE='map', _VAL={{{_TYPE='string', _VAL={'a\n', 'b'}}, 4}}},
'{"a\\u0000\\nb": 4}')
@@ -577,6 +576,8 @@ describe('json_encode() function', function()
eq('{}', eval('json_encode({})'))
eq('{"d": []}', funcs.json_encode({d={}}))
eq('{"d": [], "e": []}', funcs.json_encode({d={}, e={}}))
+ -- Empty keys not allowed (yet?) in object_to_vim() (since 7c01d5ff9286). #25564
+ -- eq('{"": []}', funcs.json_encode({['']={}}))
end)
it('cannot dump generic mapping with generic mapping keys and values',