mirror of
https://github.com/neovim/neovim.git
synced 2024-12-19 10:45:16 -07:00
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() (since7c01d5ff92
):f64e4b43e1/src/nvim/api/private/converter.c (L333-L334)
Fix #20757 Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
This commit is contained in:
parent
c84af395e8
commit
e057b38e70
4
runtime/doc/builtin.txt
generated
4
runtime/doc/builtin.txt
generated
@ -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.
|
||||
|
4
runtime/lua/vim/_meta/vimfn.lua
generated
4
runtime/lua/vim/_meta/vimfn.lua
generated
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user