From e8e3ef24637f558c5c62bfc906613789246a0f9c Mon Sep 17 00:00:00 2001 From: Milly Date: Tue, 8 Oct 2019 19:07:19 +0900 Subject: [PATCH 1/9] Do not call `type()` repeatedly. --- autoload/vital/__vital__/Web/JSON.vim | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/autoload/vital/__vital__/Web/JSON.vim b/autoload/vital/__vital__/Web/JSON.vim index 0967dd8c3..57faf0261 100644 --- a/autoload/vital/__vital__/Web/JSON.vim +++ b/autoload/vital/__vital__/Web/JSON.vim @@ -120,13 +120,14 @@ function! s:encode(val, ...) abort \ 'indent': 0, \}, get(a:000, 0, {}) \) - if type(a:val) == 0 + let t = type(a:val) + if t == 0 return a:val - elseif type(a:val) == 1 + elseif t == 1 let s = substitute(a:val, '[\x01-\x1f\\"]', '\=s:control_chars[submatch(0)]', 'g') let s = iconv(s, &encoding, 'utf-8') return '"' . s . '"' - elseif type(a:val) == 2 + elseif t == 2 if s:const.true == a:val return 'true' elseif s:const.false == a:val @@ -137,9 +138,9 @@ function! s:encode(val, ...) abort " backward compatibility return string(a:val) endif - elseif type(a:val) == 3 + elseif t == 3 return s:_encode_list(a:val, settings) - elseif type(a:val) == 4 + elseif t == 4 return s:_encode_dict(a:val, settings) else return string(a:val) From 80e1bbea33bf85d9689f3ebee5b5bdea8ed9e920 Mon Sep 17 00:00:00 2001 From: Milly Date: Tue, 8 Oct 2019 23:44:26 +0900 Subject: [PATCH 2/9] Web.JSON: Add 'allow_nan' setting to `encode()` and `decode()`. --- autoload/vital/__vital__/Web/JSON.vim | 27 +++++++++++++++++++++++++++ doc/vital/Web/JSON.txt | 23 +++++++++++++++++++++++ test/Web/JSON.vimspec | 22 ++++++++++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/autoload/vital/__vital__/Web/JSON.vim b/autoload/vital/__vital__/Web/JSON.vim index 57faf0261..15f51c339 100644 --- a/autoload/vital/__vital__/Web/JSON.vim +++ b/autoload/vital/__vital__/Web/JSON.vim @@ -38,6 +38,16 @@ let s:control_chars = { \ } lockvar s:control_chars +let s:float_constants = { + \ 'nan': 'NaN', + \ '-nan': 'NaN', + \ 'inf': 'Infinity', + \ '-inf': '-Infinity', + \ } +let s:float_nan = 0.0 / 0 +let s:float_inf = 1.0 / 0 +lockvar s:float_constants s:float_nan s:float_inf + function! s:_true() abort return 1 endfunction @@ -87,9 +97,12 @@ endfunction " @vimlint(EVL102, 1, l:null) " @vimlint(EVL102, 1, l:true) " @vimlint(EVL102, 1, l:false) +" @vimlint(EVL102, 1, l:NaN) +" @vimlint(EVL102, 1, l:Infinity) function! s:decode(json, ...) abort let settings = extend({ \ 'use_token': 0, + \ 'allow_nan': 1, \}, get(a:000, 0, {})) let json = iconv(a:json, 'utf-8', &encoding) let json = join(split(json, "\n"), '') @@ -99,6 +112,9 @@ function! s:decode(json, ...) abort let json = substitute(json, '\([\uD800-\uDBFF]\)\([\uDC00-\uDFFF]\)', \ '\=nr2char(0x10000+and(0x7ff,char2nr(submatch(1)))*0x400+and(0x3ff,char2nr(submatch(2))))', \ 'g') + if settings.allow_nan + let [NaN,Infinity] = [s:float_nan,s:float_inf] + endif if settings.use_token let prefix = '__Web.JSON__' while stridx(json, prefix) != -1 @@ -114,10 +130,13 @@ endfunction " @vimlint(EVL102, 0, l:null) " @vimlint(EVL102, 0, l:true) " @vimlint(EVL102, 0, l:false) +" @vimlint(EVL102, 0, l:NaN) +" @vimlint(EVL102, 0, l:Infinity) function! s:encode(val, ...) abort let settings = extend({ \ 'indent': 0, + \ 'allow_nan': 1, \}, get(a:000, 0, {}) \) let t = type(a:val) @@ -142,6 +161,14 @@ function! s:encode(val, ...) abort return s:_encode_list(a:val, settings) elseif t == 4 return s:_encode_dict(a:val, settings) + elseif t == 5 + let val = string(a:val) + if settings.allow_nan + let val = get(s:float_constants, val, val) + elseif has_key(s:float_constants, val) + throw 'vital: Web.JSON: Invalid float value: ' . val + endif + return val else return string(a:val) endif diff --git a/doc/vital/Web/JSON.txt b/doc/vital/Web/JSON.txt index 65dce8edf..0e03693f4 100644 --- a/doc/vital/Web/JSON.txt +++ b/doc/vital/Web/JSON.txt @@ -65,6 +65,18 @@ encode({object}[, {settings}]) *Vital.Web.JSON.encode()* " "a": 0, " "b": 1 " }' +< + 'allow_nan' + If 'allows_nan' is 0, it will raise an exception when serializing + out of range float values (nan, inf, -inf). + Otherwise 'NaN', 'Infinity' or '-Infinity' are used to represent these. + The default value is 1. + Note that NaN and Infinity are not the JSON standard. +> + echo s:JSON.encode([0.0/0, 1.0/0, -1.0/0]) + " => [NaN,Infinity,-Infinity] + echo s:JSON.encode([0.0/0], {'allow_nan': 0}) + " => vital: Web.JSON: Invalid float value: nan < decode({json}[, {settings}]) *Vital.Web.JSON.decode()* Decode a JSON string into an object that vim can treat. @@ -81,6 +93,17 @@ decode({json}[, {settings}]) *Vital.Web.JSON.decode()* echo s:JSON.decode('[true, false, null]', {'use_token': 1}) " => [s:JSON.true, s:JSON.false, s:JSON.null] < + 'allow_nan' + If 'allows_nan' is 0, it will raise an exception when deserializing + float constants ('NaN', 'Infinity', '-Infinity'). + Otherwise nan, inf or -inf are used to represent these. + The default value is 1. +> + echo s:JSON.decode('[NaN, Infinity, -Infinity]') + " => [nan, inf, -inf] + echo s:JSON.decode('[NaN]', {'allow_nan': 0}) + " => E121: Undefined variable: NaN +< ============================================================================= vim:tw=78:fo=tcq2mM:ts=8:ft=help:norl diff --git a/test/Web/JSON.vimspec b/test/Web/JSON.vimspec index 669f3c7d3..4322909db 100644 --- a/test/Web/JSON.vimspec +++ b/test/Web/JSON.vimspec @@ -81,6 +81,17 @@ Describe Web.JSON Assert Equals(JSON.decode('false', s), JSON.false) Assert Equals(JSON.decode('null', s), JSON.null) End + + It decodes special floats (NaN/Infinity/-Infinity) + Assert Equals(string(JSON.decode('NaN')), 'nan') + Assert Equals(string(JSON.decode('Infinity')), 'inf') + Assert Equals(string(JSON.decode('-Infinity')), '-inf') + + let s = { 'allow_nan': 0 } + Throws /^Vim(\w\+):E121:/ JSON.decode('NaN', s) + Throws /^Vim(\w\+):E121:/ JSON.decode('Infinity', s) + Throws /^Vim(\w\+):E121:/ JSON.decode('-Infinity', s) + End End Describe .encode() @@ -231,5 +242,16 @@ Describe Web.JSON Assert Equals(JSON.encode(JSON.false), 'false') Assert Equals(JSON.encode(JSON.null), 'null') End + + It encodes special floats (NaN/Infinity/-Infinity) + Assert Equals(JSON.encode(0.0/0), 'NaN') + Assert Equals(JSON.encode(1.0/0), 'Infinity') + Assert Equals(JSON.encode(-1.0/0), '-Infinity') + + let s = { 'allow_nan': 0 } + Throws /^vital: Web.JSON:/ JSON.encode(0.0/0, s) + Throws /^vital: Web.JSON:/ JSON.encode(1.0/0, s) + Throws /^vital: Web.JSON:/ JSON.encode(-1.0/0, s) + End End End From 34afccc604b1ccd7306e3e30033ed7761bedddab Mon Sep 17 00:00:00 2001 From: Milly Date: Wed, 9 Oct 2019 01:16:37 +0900 Subject: [PATCH 3/9] Web.JSON: Default to use vim-variables (e.g. v:true). --- autoload/vital/__vital__/Web/JSON.vim | 16 +++++++++++++--- doc/vital/Web/JSON.txt | 2 +- test/Web/JSON.vimspec | 8 ++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/autoload/vital/__vital__/Web/JSON.vim b/autoload/vital/__vital__/Web/JSON.vim index 15f51c339..caa03fb4c 100644 --- a/autoload/vital/__vital__/Web/JSON.vim +++ b/autoload/vital/__vital__/Web/JSON.vim @@ -48,16 +48,24 @@ let s:float_nan = 0.0 / 0 let s:float_inf = 1.0 / 0 lockvar s:float_constants s:float_nan s:float_inf +let s:special_constants = { + \ 'v:true': 'true', + \ 'v:false': 'false', + \ 'v:null': 'null', + \ 'v:none': 'null', + \ } +lockvar s:special_constants + function! s:_true() abort - return 1 + return v:true endfunction function! s:_false() abort - return 0 + return v:false endfunction function! s:_null() abort - return 0 + return v:null endfunction function! s:_resolve(val, prefix) abort @@ -169,6 +177,8 @@ function! s:encode(val, ...) abort throw 'vital: Web.JSON: Invalid float value: ' . val endif return val + elseif t == 6 || t == 7 + return get(s:special_constants, a:val) else return string(a:val) endif diff --git a/doc/vital/Web/JSON.txt b/doc/vital/Web/JSON.txt index 0e03693f4..30341596b 100644 --- a/doc/vital/Web/JSON.txt +++ b/doc/vital/Web/JSON.txt @@ -85,7 +85,7 @@ decode({json}[, {settings}]) *Vital.Web.JSON.decode()* 'use_token' Use special tokens (e.g. |Vital.Web.JSON.true|) to represent corresponding javascript tokens (e.g. 'true'). - Otherwise 1 or 0 are used to represent these. + Otherwise |v:true|, |v:false| or |v:null| are used to represent these. The default value is 0. > echo s:JSON.decode('[true, false, null]') diff --git a/test/Web/JSON.vimspec b/test/Web/JSON.vimspec index 4322909db..a92b66799 100644 --- a/test/Web/JSON.vimspec +++ b/test/Web/JSON.vimspec @@ -75,6 +75,9 @@ Describe Web.JSON Assert Equals(JSON.decode('true'), 1) Assert Equals(JSON.decode('false'), 0) Assert Equals(JSON.decode('null'), 0) + Assert Same(JSON.decode('true'), v:true) + Assert Same(JSON.decode('false'), v:false) + Assert Same(JSON.decode('null'), v:null) let s = { 'use_token': 1 } Assert Equals(JSON.decode('true', s), JSON.true) @@ -241,6 +244,11 @@ Describe Web.JSON Assert Equals(JSON.encode(JSON.true), 'true') Assert Equals(JSON.encode(JSON.false), 'false') Assert Equals(JSON.encode(JSON.null), 'null') + + Assert Equals(JSON.encode(v:true), 'true') + Assert Equals(JSON.encode(v:false), 'false') + Assert Equals(JSON.encode(v:null), 'null') + Assert Equals(JSON.encode(v:none), 'null') End It encodes special floats (NaN/Infinity/-Infinity) From 1cf223527a6722d924aa951b5ea15421d764d8b8 Mon Sep 17 00:00:00 2001 From: Milly Date: Mon, 11 Nov 2019 11:39:51 +0900 Subject: [PATCH 4/9] Web.JSON: Convert encoding. Add 'from_encoding' setting to `encode()`. --- autoload/vital/__vital__/Web/JSON.vim | 23 ++++++++++++-------- test/Web/JSON.vimspec | 31 +++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/autoload/vital/__vital__/Web/JSON.vim b/autoload/vital/__vital__/Web/JSON.vim index caa03fb4c..9358ef823 100644 --- a/autoload/vital/__vital__/Web/JSON.vim +++ b/autoload/vital/__vital__/Web/JSON.vim @@ -145,14 +145,19 @@ function! s:encode(val, ...) abort let settings = extend({ \ 'indent': 0, \ 'allow_nan': 1, + \ 'from_encoding': &encoding, \}, get(a:000, 0, {}) \) + return s:_encode(a:val, settings) +endfunction + +function! s:_encode(val, settings) abort let t = type(a:val) if t == 0 return a:val elseif t == 1 - let s = substitute(a:val, '[\x01-\x1f\\"]', '\=s:control_chars[submatch(0)]', 'g') - let s = iconv(s, &encoding, 'utf-8') + let s = iconv(a:val, a:settings.from_encoding, 'utf-8') + let s = substitute(s, '[\x01-\x1f\\"]', '\=s:control_chars[submatch(0)]', 'g') return '"' . s . '"' elseif t == 2 if s:const.true == a:val @@ -166,12 +171,12 @@ function! s:encode(val, ...) abort return string(a:val) endif elseif t == 3 - return s:_encode_list(a:val, settings) + return s:_encode_list(a:val, a:settings) elseif t == 4 - return s:_encode_dict(a:val, settings) + return s:_encode_dict(a:val, a:settings) elseif t == 5 let val = string(a:val) - if settings.allow_nan + if a:settings.allow_nan let val = get(s:float_constants, val, val) elseif has_key(s:float_constants, val) throw 'vital: Web.JSON: Invalid float value: ' . val @@ -189,7 +194,7 @@ function! s:_encode_list(val, settings) abort if empty(a:val) return '[]' elseif !a:settings.indent - let encoded_candidates = map(copy(a:val), 's:encode(v:val, a:settings)') + let encoded_candidates = map(copy(a:val), 's:_encode(v:val, a:settings)') return printf('[%s]', join(encoded_candidates, ',')) else let previous_indent = get(a:settings, '_previous_indent') @@ -199,7 +204,7 @@ function! s:_encode_list(val, settings) abort \}) let encoded_candidates = map( \ copy(a:val), - \ printf('''%s'' . s:encode(v:val, ns)', repeat(' ', indent)), + \ printf('''%s'' . s:_encode(v:val, ns)', repeat(' ', indent)), \) return printf( \ "[\n%s\n%s]", @@ -216,7 +221,7 @@ function! s:_encode_dict(val, settings) abort return '{}' elseif !a:settings.indent let encoded_candidates = map(keys(a:val), - \ 's:encode(v:val, a:settings) . '':'' . s:encode(a:val[v:val], a:settings)' + \ 's:_encode(v:val, a:settings) . '':'' . s:_encode(a:val[v:val], a:settings)' \) return printf('{%s}', join(encoded_candidates, ',')) else @@ -227,7 +232,7 @@ function! s:_encode_dict(val, settings) abort \}) let encoded_candidates = map(keys(a:val), \ printf( - \ '''%s'' . s:encode(v:val, ns) . '': '' . s:encode(a:val[v:val], ns)', + \ '''%s'' . s:_encode(v:val, ns) . '': '' . s:_encode(a:val[v:val], ns)', \ repeat(' ', indent), \ ), \) diff --git a/test/Web/JSON.vimspec b/test/Web/JSON.vimspec index a92b66799..16480d9fe 100644 --- a/test/Web/JSON.vimspec +++ b/test/Web/JSON.vimspec @@ -42,6 +42,20 @@ Describe Web.JSON Assert Equals(JSON.decode('"\ud83c\u00a0"'), "\ud83c\u00a0") End + It decodes strings with character encoding + let expected_utf8 = "sあc漢g" + Assert Equals(JSON.decode('"sあc漢g"'), expected_utf8) + + let expected_sjis = iconv("sあc漢g", 'utf-8', 'sjis') + try + let &encoding = 'sjis' + let res = JSON.decode('"sあc漢g"') + finally + let &encoding = 'utf-8' + endtry + Assert Equals(res, expected_sjis) + End + It decodes lists Assert Equals(JSON.decode('[]'), []) Assert Equals(JSON.decode('[0, 1, 2]'), [0, 1, 2]) @@ -126,6 +140,23 @@ Describe Web.JSON Assert Equals(JSON.encode("\xf0\x9f\x8d\xa3"), "\"\xf0\x9f\x8d\xa3\"") End + It encodes strings with character encoding + let str_utf8 = "sあc漢g" + Assert Equals(JSON.encode(str_utf8), '"sあc漢g"') + + let str_sjis = iconv("sあc漢g", 'utf-8', 'sjis') + try + let &encoding = 'sjis' + let res = JSON.encode(str_sjis) + finally + let &encoding = 'utf-8' + endtry + Assert Equals(res, '"sあc漢g"') + + let s = { 'from_encoding': 'sjis' } + Assert Equals(JSON.encode(str_sjis, s), '"sあc漢g"') + End + It encodes lists Assert Equals(JSON.encode([]), '[]') Assert Equals(JSON.encode([0, 1, 2]), '[0,1,2]') From 416240dba54aec3c6b0a620cd34388c3f3767f40 Mon Sep 17 00:00:00 2001 From: Milly Date: Mon, 11 Nov 2019 12:52:18 +0900 Subject: [PATCH 5/9] Web.JSON: Add 'ensure_ascii' setting to `encode()`. --- autoload/vital/__vital__/Web/JSON.vim | 18 +++++++++++++++++- doc/vital/Web/JSON.txt | 12 ++++++++++++ test/Web/JSON.vimspec | 8 ++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/autoload/vital/__vital__/Web/JSON.vim b/autoload/vital/__vital__/Web/JSON.vim index 9358ef823..8453dfb9a 100644 --- a/autoload/vital/__vital__/Web/JSON.vim +++ b/autoload/vital/__vital__/Web/JSON.vim @@ -146,9 +146,25 @@ function! s:encode(val, ...) abort \ 'indent': 0, \ 'allow_nan': 1, \ 'from_encoding': &encoding, + \ 'ensure_ascii': 0, \}, get(a:000, 0, {}) \) - return s:_encode(a:val, settings) + let json = s:_encode(a:val, settings) + if settings.ensure_ascii + let json = substitute(json, '[\U0000007f-\U0010ffff]', + \ {m -> s:_escape_unicode_chars(m[0])}, 'g') + endif + return json +endfunction + +function! s:_escape_unicode_chars(char) abort + let n = char2nr(a:char) + if n < 0x10000 + return printf('\u%04x', n) + else + let n -= 0x10000 + return printf('\u%04x%\u%04x', 0xd800 + n / 0x400, 0xdc00 + and(0x3ff, n)) + endif endfunction function! s:_encode(val, settings) abort diff --git a/doc/vital/Web/JSON.txt b/doc/vital/Web/JSON.txt index 30341596b..2052fccee 100644 --- a/doc/vital/Web/JSON.txt +++ b/doc/vital/Web/JSON.txt @@ -77,6 +77,18 @@ encode({object}[, {settings}]) *Vital.Web.JSON.encode()* " => [NaN,Infinity,-Infinity] echo s:JSON.encode([0.0/0], {'allow_nan': 0}) " => vital: Web.JSON: Invalid float value: nan +< + 'ensure_ascii' + If 'ensure_ascii' is 0, all characters without control-chars + (0x01-0x1f) will be output as-is. + Otherwise the output is guaranteed to have all incoming non-ASCII + characters escaped. + The default value is 0. +> + echo s:JSON.encode(["foo", "bár", "\n"]) + " => '["foo","bár","\n"]' + echo s:JSON.encode(["foo", "bár", "\n"], {'ensure_ascii': 1}) + " => '["foo","b\u00e1r","\n"]' < decode({json}[, {settings}]) *Vital.Web.JSON.decode()* Decode a JSON string into an object that vim can treat. diff --git a/test/Web/JSON.vimspec b/test/Web/JSON.vimspec index 16480d9fe..1ec077f04 100644 --- a/test/Web/JSON.vimspec +++ b/test/Web/JSON.vimspec @@ -157,6 +157,14 @@ Describe Web.JSON Assert Equals(JSON.encode(str_sjis, s), '"sあc漢g"') End + It encodes strings with {settings.ensure_ascii} = true + let s = { 'ensure_ascii': v:true } + " multibyte + Assert Equals(JSON.encode("s¢cĴgё", s), '"s\u00a2c\u0134g\u0451"') + " UTF-16 surrogate pair + Assert Equals(JSON.encode("su\xf0\x9f\x8d\xa3shi", s), '"su\ud83c\udf63shi"') + End + It encodes lists Assert Equals(JSON.encode([]), '[]') Assert Equals(JSON.encode([0, 1, 2]), '[0,1,2]') From 04349a919fc2b78b110d8f2818247b16f0e0049a Mon Sep 17 00:00:00 2001 From: Milly Date: Mon, 11 Nov 2019 13:38:23 +0900 Subject: [PATCH 6/9] Web.JSON: Encode blob to list. --- autoload/vital/__vital__/Web/JSON.vim | 5 ++++- test/Web/JSON.vimspec | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/autoload/vital/__vital__/Web/JSON.vim b/autoload/vital/__vital__/Web/JSON.vim index 8453dfb9a..a5d9f3925 100644 --- a/autoload/vital/__vital__/Web/JSON.vim +++ b/autoload/vital/__vital__/Web/JSON.vim @@ -96,10 +96,11 @@ endfunction function! s:_vital_loaded(V) abort let s:V = a:V let s:string = s:V.import('Data.String') + let s:bytes = s:V.import('Data.List.Byte') endfunction function! s:_vital_depends() abort - return ['Data.String'] + return ['Data.String', 'Data.List.Byte'] endfunction " @vimlint(EVL102, 1, l:null) @@ -200,6 +201,8 @@ function! s:_encode(val, settings) abort return val elseif t == 6 || t == 7 return get(s:special_constants, a:val) + elseif t == 10 + return s:_encode_list(s:bytes.from_blob(a:val), a:settings) else return string(a:val) endif diff --git a/test/Web/JSON.vimspec b/test/Web/JSON.vimspec index 1ec077f04..1f15aa335 100644 --- a/test/Web/JSON.vimspec +++ b/test/Web/JSON.vimspec @@ -300,5 +300,13 @@ Describe Web.JSON Throws /^vital: Web.JSON:/ JSON.encode(1.0/0, s) Throws /^vital: Web.JSON:/ JSON.encode(-1.0/0, s) End + + It encodes blob + if !has('patch-8.1.0735') + Skip no available blob type. + endif + Assert Equals(JSON.encode(0z), '[]') + Assert Equals(JSON.encode(0zFF.00.ED.01.5D.AF), '[255,0,237,1,93,175]') + End End End From 3044278ced9b857954250abfa39c06b12df4ba64 Mon Sep 17 00:00:00 2001 From: Milly Date: Mon, 11 Nov 2019 14:17:25 +0900 Subject: [PATCH 7/9] Web.JSON: Throws exception when encode funcref, job or channel. --- autoload/vital/__vital__/Web/JSON.vim | 6 +----- test/Web/JSON.vimspec | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/autoload/vital/__vital__/Web/JSON.vim b/autoload/vital/__vital__/Web/JSON.vim index a5d9f3925..395d21bc5 100644 --- a/autoload/vital/__vital__/Web/JSON.vim +++ b/autoload/vital/__vital__/Web/JSON.vim @@ -183,9 +183,6 @@ function! s:_encode(val, settings) abort return 'false' elseif s:const.null == a:val return 'null' - else - " backward compatibility - return string(a:val) endif elseif t == 3 return s:_encode_list(a:val, a:settings) @@ -203,9 +200,8 @@ function! s:_encode(val, settings) abort return get(s:special_constants, a:val) elseif t == 10 return s:_encode_list(s:bytes.from_blob(a:val), a:settings) - else - return string(a:val) endif + throw 'vital: Web.JSON: Invalid argument: ' . string(a:val) endfunction " @vimlint(EVL102, 1, l:ns) diff --git a/test/Web/JSON.vimspec b/test/Web/JSON.vimspec index 1f15aa335..ea2a595cd 100644 --- a/test/Web/JSON.vimspec +++ b/test/Web/JSON.vimspec @@ -308,5 +308,26 @@ Describe Web.JSON Assert Equals(JSON.encode(0z), '[]') Assert Equals(JSON.encode(0zFF.00.ED.01.5D.AF), '[255,0,237,1,93,175]') End + + It throws exception when encodes funcref + let Funcref = function('get') + Throws /^vital: Web\.JSON: Invalid argument: / JSON.encode(Funcref) + End + + It throws exception when encodes job + if !exists('*test_null_job') + Skip no available job type. + endif + let job = test_null_job() + Throws /^vital: Web\.JSON: Invalid argument: / JSON.encode(job) + End + + It throws exception when encodes channel + if !exists('*test_null_channel') + Skip no available channel type. + endif + let ch = test_null_channel() + Throws /^vital: Web\.JSON: Invalid argument: / JSON.encode(ch) + End End End From c661d017ac94f32e46a390499ae261f2e8803f5f Mon Sep 17 00:00:00 2001 From: Milly Date: Mon, 11 Nov 2019 14:54:44 +0900 Subject: [PATCH 8/9] Web.JSON: Obsolute constants true, false and null. --- autoload/vital/__vital__/Web/JSON.vim | 53 +++----------------------- doc/vital/Web/JSON.txt | 54 ++++++++++++--------------- test/Web/JSON.vimspec | 15 ++------ 3 files changed, 32 insertions(+), 90 deletions(-) diff --git a/autoload/vital/__vital__/Web/JSON.vim b/autoload/vital/__vital__/Web/JSON.vim index 395d21bc5..5ee1328a1 100644 --- a/autoload/vital/__vital__/Web/JSON.vim +++ b/autoload/vital/__vital__/Web/JSON.vim @@ -56,38 +56,13 @@ let s:special_constants = { \ } lockvar s:special_constants -function! s:_true() abort - return v:true -endfunction - -function! s:_false() abort - return v:false -endfunction - -function! s:_null() abort - return v:null -endfunction - -function! s:_resolve(val, prefix) abort - let t = type(a:val) - if t == type('') - let m = matchlist(a:val, '^' . a:prefix . '\(null\|true\|false\)$') - if !empty(m) - return s:const[m[1]] - endif - elseif t == type([]) || t == type({}) - return map(a:val, 's:_resolve(v:val, a:prefix)') - endif - return a:val -endfunction - function! s:_vital_created(module) abort " define constant variables if !exists('s:const') let s:const = {} - let s:const.true = function('s:_true') - let s:const.false = function('s:_false') - let s:const.null = function('s:_null') + let s:const.true = v:true + let s:const.false = v:false + let s:const.null = v:null lockvar s:const endif call extend(a:module, s:const) @@ -110,7 +85,6 @@ endfunction " @vimlint(EVL102, 1, l:Infinity) function! s:decode(json, ...) abort let settings = extend({ - \ 'use_token': 0, \ 'allow_nan': 1, \}, get(a:000, 0, {})) let json = iconv(a:json, 'utf-8', &encoding) @@ -124,17 +98,8 @@ function! s:decode(json, ...) abort if settings.allow_nan let [NaN,Infinity] = [s:float_nan,s:float_inf] endif - if settings.use_token - let prefix = '__Web.JSON__' - while stridx(json, prefix) != -1 - let prefix .= '_' - endwhile - let [null,true,false] = map(['null','true','false'], 'prefix . v:val') - sandbox return s:_resolve(eval(json), prefix) - else - let [null,true,false] = [s:const.null(),s:const.true(),s:const.false()] - sandbox return eval(json) - endif + let [null, true, false] = [v:null, v:true, v:false] + sandbox return eval(json) endfunction " @vimlint(EVL102, 0, l:null) " @vimlint(EVL102, 0, l:true) @@ -176,14 +141,6 @@ function! s:_encode(val, settings) abort let s = iconv(a:val, a:settings.from_encoding, 'utf-8') let s = substitute(s, '[\x01-\x1f\\"]', '\=s:control_chars[submatch(0)]', 'g') return '"' . s . '"' - elseif t == 2 - if s:const.true == a:val - return 'true' - elseif s:const.false == a:val - return 'false' - elseif s:const.null == a:val - return 'null' - endif elseif t == 3 return s:_encode_list(a:val, a:settings) elseif t == 4 diff --git a/doc/vital/Web/JSON.txt b/doc/vital/Web/JSON.txt index 2052fccee..184cce31e 100644 --- a/doc/vital/Web/JSON.txt +++ b/doc/vital/Web/JSON.txt @@ -23,34 +23,39 @@ INTERFACE *Vital.Web.JSON-interface* CONSTS *Vital.Web.JSON-consts* true *Vital.Web.JSON.true* - It is used to indicate 'true' in JSON string. It is represented as a - |Funcref| thus if you assign the value to a variable which name does not - start with a capital, "s:", "w:", "t:" or "b:" will raise an exception. - This returns 1 when you use it as a function. + It is |v:true|. This is left for backward compatibility. false *Vital.Web.JSON.false* - It is used to indicate 'false' in JSON string. It is represented as a - |Funcref| thus if you assign the value to a variable which name does not - start with a capital, "s:", "w:", "t:" or "b:" will raise an exception. - This returns 0 when you use it as a function. + It is |v:false|. This is left for backward compatibility. null *Vital.Web.JSON.null* - It is used to indicate 'null' in JSON string. It is represented as a - |Funcref| thus if you assign the value to a variable which name does not - start with a capital, "s:", "w:", "t:" or "b:" will raise an exception. - This returns 0 when you use it as a function. + It is |v:null|. This is left for backward compatibility. ------------------------------------------------------------------------------ FUNCTIONS *Vital.Web.JSON-functions* encode({object}[, {settings}]) *Vital.Web.JSON.encode()* - Encode an object into a JSON string. Special tokens - (e.g. |Vital.Web.JSON.true|) are encoded into corresponding javascript - tokens (e.g. 'true'). -> - echo s:JSON.encode([s:JSON.true, s:JSON.false, s:JSON.null]) - " => '[true, false, null]' -< + Encode an object into a JSON string. + Vim values are converted as follows: + |Number| decimal number + |Float| floating point number + Float nan "NaN" + Float inf "Infinity" + Float -inf "-Infinity" + |String| in double quotes (possibly null) + |List| as an array (possibly null); when + used recursively: [] + |Dict| as an object (possibly null); when + used recursively: {} + |Blob| as an array of the individual bytes + v:false "false" + v:true "true" + v:none "null" + v:null "null" + |Funcref| not possible, error + |job| not possible, error + |channel| not possible, error + {settings} is a |Dictionary| which allows the following: 'indent' @@ -94,17 +99,6 @@ decode({json}[, {settings}]) *Vital.Web.JSON.decode()* Decode a JSON string into an object that vim can treat. {settings} is a |Dictionary| which allows the following: - 'use_token' - Use special tokens (e.g. |Vital.Web.JSON.true|) to represent - corresponding javascript tokens (e.g. 'true'). - Otherwise |v:true|, |v:false| or |v:null| are used to represent these. - The default value is 0. -> - echo s:JSON.decode('[true, false, null]') - " => [1, 0, 0] - echo s:JSON.decode('[true, false, null]', {'use_token': 1}) - " => [s:JSON.true, s:JSON.false, s:JSON.null] -< 'allow_nan' If 'allows_nan' is 0, it will raise an exception when deserializing float constants ('NaN', 'Infinity', '-Infinity'). diff --git a/test/Web/JSON.vimspec b/test/Web/JSON.vimspec index ea2a595cd..9b35e740d 100644 --- a/test/Web/JSON.vimspec +++ b/test/Web/JSON.vimspec @@ -5,9 +5,9 @@ Describe Web.JSON Describe .constants() It should have constant variables which indicate the special tokens - Assert Match(string(JSON.true), "function('\.*_true')") - Assert Match(string(JSON.false), "function('\.*_false')") - Assert Match(string(JSON.null), "function('\.*_null')") + Assert Same(JSON.true, v:true) + Assert Same(JSON.false, v:false) + Assert Same(JSON.null, v:null) End End @@ -85,18 +85,9 @@ Describe Web.JSON End It decodes special tokens (true/false/null) - " true/false/null - Assert Equals(JSON.decode('true'), 1) - Assert Equals(JSON.decode('false'), 0) - Assert Equals(JSON.decode('null'), 0) Assert Same(JSON.decode('true'), v:true) Assert Same(JSON.decode('false'), v:false) Assert Same(JSON.decode('null'), v:null) - - let s = { 'use_token': 1 } - Assert Equals(JSON.decode('true', s), JSON.true) - Assert Equals(JSON.decode('false', s), JSON.false) - Assert Equals(JSON.decode('null', s), JSON.null) End It decodes special floats (NaN/Infinity/-Infinity) From f8de29ba1fc4ec665dbb8fee9eb363e23679aef0 Mon Sep 17 00:00:00 2001 From: Milly Date: Thu, 21 May 2020 00:41:12 +0900 Subject: [PATCH 9/9] Web.JSON: Use Vim.Type for detecting type. --- autoload/vital/__vital__/Web/JSON.vim | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/autoload/vital/__vital__/Web/JSON.vim b/autoload/vital/__vital__/Web/JSON.vim index 5ee1328a1..fabd6c6f5 100644 --- a/autoload/vital/__vital__/Web/JSON.vim +++ b/autoload/vital/__vital__/Web/JSON.vim @@ -72,10 +72,11 @@ function! s:_vital_loaded(V) abort let s:V = a:V let s:string = s:V.import('Data.String') let s:bytes = s:V.import('Data.List.Byte') + let s:t = s:V.import('Vim.Type').types endfunction function! s:_vital_depends() abort - return ['Data.String', 'Data.List.Byte'] + return ['Data.String', 'Data.List.Byte', 'Vim.Type'] endfunction " @vimlint(EVL102, 1, l:null) @@ -135,17 +136,17 @@ endfunction function! s:_encode(val, settings) abort let t = type(a:val) - if t == 0 + if t ==# s:t.number return a:val - elseif t == 1 + elseif t ==# s:t.string let s = iconv(a:val, a:settings.from_encoding, 'utf-8') let s = substitute(s, '[\x01-\x1f\\"]', '\=s:control_chars[submatch(0)]', 'g') return '"' . s . '"' - elseif t == 3 + elseif t ==# s:t.list return s:_encode_list(a:val, a:settings) - elseif t == 4 + elseif t ==# s:t.dict return s:_encode_dict(a:val, a:settings) - elseif t == 5 + elseif t ==# s:t.float let val = string(a:val) if a:settings.allow_nan let val = get(s:float_constants, val, val) @@ -153,9 +154,9 @@ function! s:_encode(val, settings) abort throw 'vital: Web.JSON: Invalid float value: ' . val endif return val - elseif t == 6 || t == 7 + elseif t ==# s:t.bool || t ==# s:t.none return get(s:special_constants, a:val) - elseif t == 10 + elseif t ==# s:t.blob return s:_encode_list(s:bytes.from_blob(a:val), a:settings) endif throw 'vital: Web.JSON: Invalid argument: ' . string(a:val)