local encode, decode, dkencode, dkdecode local test_module, opt = ... -- command line argument --local test_module = 'cmj-json' --local test_module = 'dkjson' --local test_module = 'dkjson-nopeg' --local test_module = 'fleece' --local test_module = 'jf-json' --locel test_module = 'lua-yajl' --local test_module = 'mp-cjson' --local test_module = 'nm-json' --local test_module = 'sb-json' --local test_module = 'th-json' test_module = test_module or 'dkjson' --local opt = "esc" -- Test which characters in the BMP get escaped and whether this is correct --local opt = "esc_full" -- Full range from 0 to 0x10ffff --local opt = "esc_asc" -- Just 0 to 127 --local opt = "refcycle" -- What happens when a reference cycle gets encoded? local testlocale = "de_DE.UTF8" local function inlocale(fn) local oldloc = os.setlocale(nil, 'numeric') if not os.setlocale(testlocale, 'numeric') then print("test could not switch to locale "..testlocale) else fn() end os.setlocale(oldloc, 'numeric') end if test_module == 'dkjson-nopeg' then test_module = 'dkjson' package.preload["lpeg"] = function () error "lpeg disabled" end package.loaded["lpeg"] = nil lpeg = nil end if test_module == 'dkjson-lulpeg' then test_module = 'dkjson' package.loaded["lpeg"] = require "lulpeg" end do -- http://chiselapp.com/user/dhkolf/repository/dkjson/ local dkjson = require "dkjson" dkencode = dkjson.encode dkdecode = dkjson.decode end if test_module == 'cmj-json' then -- https://github.com/craigmj/json4lua/ -- http://json.luaforge.net/ local json = require "cmjjson" -- renamed, the original file was just 'json' encode = json.encode decode = json.decode elseif test_module == 'dkjson' then -- http://chiselapp.com/user/dhkolf/repository/dkjson/ encode = dkencode decode = dkdecode elseif test_module == 'fleece' then -- http://www.eonblast.com/fleece/ local fleece = require "fleece" encode = function(x) return fleece.json(x, "E4") end elseif test_module == 'jf-json' then -- http://regex.info/blog/lua/json local json = require "jfjson" -- renamed, the original file was just 'JSON' encode = function(x) return json:encode(x) end decode = function(x) return json:decode(x) end elseif test_module == 'lua-yajl' then -- http://github.com/brimworks/lua-yajl local yajl = require ("yajl") encode = yajl.to_string decode = yajl.to_value elseif test_module == 'mp-cjson' then -- http://www.kyne.com.au/~mark/software/lua-cjson.php local json = require "cjson" encode = json.encode decode = json.decode elseif test_module == 'nm-json' then -- http://luaforge.net/projects/luajsonlib/ local json = require "LuaJSON" encode = json.encode or json.stringify decode = json.decode or json.parse elseif test_module == 'sb-json' then -- http://www.chipmunkav.com/downloads/Json.lua local json = require "sbjson" -- renamed, the original file was just 'Json' encode = json.Encode decode = json.Decode elseif test_module == 'th-json' then -- https://github.com/harningt/luajson -- http://luaforge.net/projects/luajson/ local json = require "json" encode = json.encode decode = json.decode else print "No module specified" return end if not encode then print ("No encode method") else local x, r local escapecodes = { ["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t", ["/"] = "\\/" } local function test (x, n, expect) local enc = encode{ x }:match("^%s*%[%s*%\"(.-)%\"%s*%]%s*$") if not enc or (escapecodes[x] ~= enc and ("\\u%04x"):format(n) ~= enc:gsub("[A-F]", string.lower) and not (expect and enc:match("^"..expect.."$"))) then print(("U+%04X isn't encoded correctly: %q"):format(n, enc)) end end -- necessary escapes for JSON: for i = 0,31 do test(string.char(i), i) end test("\"", ("\""):byte()) test("\\", ("\\"):byte()) -- necessary escapes for JavaScript: test("\226\128\168", 0x2028) test("\226\128\169", 0x2029) -- invalid escapes that were seen in the wild: test("'", ("'"):byte(), "%'") r,x = pcall (encode, { [1000] = "x" }) if not r then print ("encoding a sparse array (#=0) raises an error:", x) else if #x > 30 then print ("sparse array (#=0) encoded as:", x:sub(1,15).." <...> "..x:sub(-15,-1), "#"..#x) else print ("sparse array (#=0) encoded as:", x) end end r,x = pcall (encode, { [1] = "a", [1000] = "x" }) if not r then print ("encoding a sparse array (#=1) raises an error:", x) else if #x > 30 then print ("sparse array (#=1) encoded as:", x:sub(1,15).." <...> "..x:sub(-15,-1), "#str="..#x) else print ("sparse array (#=1) encoded as:", x) end end r,x = pcall (encode, { [1] = "a", [5] = "c", ["x"] = "x" }) if not r then print ("encoding a mixed table raises an error:", x) else print ("mixed table encoded as:", x) end r, x = pcall(encode, { math.huge*0 }) -- NaN if not r then print ("encoding NaN raises an error:", x) else r = dkdecode(x) if not r then print ("NaN isn't converted into valid JSON:", x) elseif type(r[1]) == "number" and r[1] == r[1] then -- a number, but not NaN print ("NaN is converted into a valid number:", x) else print ("NaN is converted to:", x) end end if test_module == 'fleece' then print ("Fleece (0.3.1) is known to freeze on +/-Inf") else r, x = pcall(encode, { math.huge }) -- +Inf if not r then print ("encoding +Inf raises an error:", x) else r = dkdecode(x) if not r then print ("+Inf isn't converted into valid JSON:", x) else print ("+Inf is converted to:", x) end end r, x = pcall(encode, { -math.huge }) -- -Inf if not r then print ("encoding -Inf raises an error:", x) else r = dkdecode(x) if not r then print ("-Inf isn't converted into valid JSON:", x) else print ("-Inf is converted to:", x) end end end inlocale(function () local r, x = pcall(encode, { 0.5 }) if not r then print("encoding 0.5 in locale raises an error:", x) elseif not x:find(".", 1, true) then print("In locale 0.5 isn't converted into valid JSON:", x) end end) -- special tests for dkjson: if test_module == 'dkjson' then do -- encode a function local why, value, exstate local state = { exception = function (w, v, s) why, value, exstate = w, v, s return "\"demo\"" end } local encfunction = function () end r, x = pcall(dkencode, { encfunction }, state ) if not r then print("encoding a function with exception handler raises an error:", x) else if x ~= "[\"demo\"]" then print("expected to see output of exception handler for type exception, but got", x) end if why ~= "unsupported type" then print("expected exception reason to be 'unsupported type' for type exception") end if value ~= encfunction then print("expected to recieve value for type exception") end if exstate ~= state then print("expected to recieve state for type exception") end end r, x = pcall(dkencode, { function () end }, { exception = function (w, v, s) return nil, "demo" end }) if r or x ~= "demo" then print("expected custom error for type exception, but got:", r, x) end r, x = pcall(dkencode, { function () end }, { exception = function (w, v, s) return nil end }) if r or x ~= "type 'function' is not supported by JSON." then print("expected default error for type exception, but got:", r, x) end end do -- encode a reference cycle local why, value, exstate local state = { exception = function (w, v, s) why, value, exstate = w, v, s return "\"demo\"" end } local a = {} a[1] = a r, x = pcall(dkencode, a, state ) if not r then print("encoding a reference cycle with exception handler raises an error:", x) else if x ~= "[\"demo\"]" then print("expected to see output of exception handler for reference cycle exception, but got", x) end if why ~= "reference cycle" then print("expected exception reason to be 'reference cycle' for reference cycle exception") end if value ~= a then print("expected to recieve value for reference cycle exception") end if exstate ~= state then print("expected to recieve state for reference cycle exception") end end end do -- example exception handler r = dkencode(function () end, { exception = require "dkjson".encodeexception }) if r ~= [[""]] then print("expected the exception encoder to encode default error message, but got", r) end end do -- test state buffer for custom __tojson function local origstate = {} local usedstate, usedbuffer, usedbufferlen dkencode({ setmetatable({}, { __tojson = function(self, state) usedstate = state usedbuffer = state.buffer usedbufferlen = state.bufferlen return true end }) }, origstate) if usedstate ~= origstate then print("expected tojson-function to recieve the original state") end if type(usedbuffer) ~= 'table' or #usedbuffer < 1 then print("expected buffer in tojson-function to be an array") end if usedbufferlen ~= 1 then print("expected bufferlen in tojson-function to be 1, but got "..tostring(usedbufferlen)) end end do -- do not keep buffer and bufferlen when they were not present initially local origstate = {} dkencode(setmetatable({}, {__tojson = function() return true end}), origstate) if origstate.buffer ~= nil then print("expected buffer to be reset to nil") end if origstate.bufferlen ~= nil then print("expected bufferlen to be reset to nil") end end do -- keep buffer and update bufferlen when they were present initially local origbuffer = {} local origstate = { buffer = origbuffer } dkencode(true, origstate) if origstate.buffer ~= origbuffer then print("expected original buffer to remain") end if origstate.bufferlen ~= 1 then print("expected bufferlen to be updated") end end end end if not decode then print ("No decode method") else local x, r x = decode[=[ ["\u0000"] ]=] if x[1] ~= "\000" then print ("\\u0000 isn't decoded correctly") end x = decode[=[ ["\u20AC"] ]=] if x[1] ~= "\226\130\172" then print ("\\u20AC isn't decoded correctly") end x = decode[=[ ["\uD834\uDD1E"] ]=] if x[1] ~= "\240\157\132\158" then print ("\\uD834\\uDD1E isn't decoded correctly") end r, x = pcall(decode, [=[ {"x":{"x":{"x":{"x":{"x": {"x":{"x":{"x":{"x":{"x": {"x":{"x":{"x":{"x":{"x": {"x":{"x":{"x":{"x":{"x": {"x":{"x":{"x":{"x":{"x": {"x":{"x":{"x":{"x":{"x": {"x":{"x":{"x":{"x":{"x": {"x":{"x":{"x":{"x":{"x": {"x":{"x":{"x":{"x":{"x": {"x":{"x":{"x":{"x":{"x": {"x":{"x":{"x":{"x":{"x": {"x":{"x":{"x":{"x":{"x": "deep down" } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } } ]=]) if not r then print ("decoding a deep nested table raises an error:", x) else local i = 0 while type(x) == 'table' do i = i + 1 x = x.x end if i ~= 60 or x ~= "deep down" then print ("deep nested table isn't decoded correctly") end end if false and test_module == 'cmj-json' then -- unfortunatly the version can't be read print ("decoding a big array takes ages (or forever?) on cmj-json prior to version 0.9.5") else r, x = pcall(decode, "["..("0,"):rep(100000).."0]") if not r then print ("decoding a big array raises an error:", x) else if type(x) ~= 'table' or #x ~= 100001 then print ("big array isn't decoded correctly") end end end r, x = pcall(decode, "{}") if not r then print ("decoding an empty object raises an error:", x) end r, x = pcall(decode, "[]") if not r then print ("decoding an empty array raises an error:", x) end r, x = pcall(decode, "[1e+2]") if not r then print ("decoding a number with exponential notation raises an error:", x) elseif x[1] ~= 1e+2 then print ("1e+2 decoded incorrectly:", r[1]) end inlocale(function () local r, x = pcall(decode, "[0.5]") if not r then print("decoding 0.5 in locale raises an error:", x) elseif not x then print("cannot decode 0.5 in locale") elseif x[1] ~= 0.5 then print("decoded 0.5 incorrectly in locale:", x[1]) end end) -- special tests for dkjson: if test_module == 'dkjson' then x = dkdecode[=[ [{"x":0}] ]=] local m = getmetatable(x) if not m or m.__jsontype ~= 'array' then print (".__jsontype ~= array") end local m = getmetatable(x[1]) if not m or m.__jsontype ~= 'object' then print (".__jsontype ~= object") end local x,p,m = dkdecode" invalid " if p ~= 2 or type(m) ~= 'string' or not m:find("at line 1, column 2$") then print (("Invalid location: position=%d, message=%q"):format(p,m)) end local x,p,m = dkdecode" \n invalid " if p ~= 4 or type(m) ~= 'string' or not m:find("at line 2, column 2$") then print (("Invalid location: position=%d, message=%q"):format(p,m)) end do -- single line comments local x, p, m = dkdecode [[ {"test://" // comment // --? : [ // continues 0] // } ]] if type(x) ~= 'table' or type(x["test://"]) ~= 'table' or x["test://"][1] ~= 0 then print("could not decode a string with single line comments: "..tostring(m)) end end do -- multi line comments local x, p, m = dkdecode [[ {"test:/*"/**//* hi! this is a comment */ : [/** / **/ 0] } ]] if type(x) ~= 'table' or type(x["test:/*"]) ~= 'table' or x["test:/*"][1] ~= 0 then print("could not decode a string with multi line comments: "..tostring(m)) end end end end if encode and opt == "refcycle" then local a = {} a.a = a print ("Trying a reference cycle...") encode(a) end if encode and (opt or ""):sub(1,3) == "esc" then local strchar, strbyte, strformat = string.char, string.byte, string.format local floor = math.floor local function unichar (value) if value < 0 then return nil elseif value <= 0x007f then return strchar (value) elseif value <= 0x07ff then return strchar (0xc0 + floor(value/0x40), 0x80 + (floor(value) % 0x40)) elseif value <= 0xffff then return strchar (0xe0 + floor(value/0x1000), 0x80 + (floor(value/0x40) % 0x40), 0x80 + (floor(value) % 0x40)) elseif value <= 0x10ffff then return strchar (0xf0 + floor(value/0x40000), 0x80 + (floor(value/0x1000) % 0x40), 0x80 + (floor(value/0x40) % 0x40), 0x80 + (floor(value) % 0x40)) else return nil end end local escapecodes = { ["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t", ["/"] = "\\/" } local function escapeutf8 (uchar) local a, b, c, d = strbyte (uchar, 1, 4) a, b, c, d = a or 0, b or 0, c or 0, d or 0 if a <= 0x7f then value = a elseif 0xc0 <= a and a <= 0xdf and b >= 0x80 then value = (a - 0xc0) * 0x40 + b - 0x80 elseif 0xe0 <= a and a <= 0xef and b >= 0x80 and c >= 0x80 then value = ((a - 0xe0) * 0x40 + b - 0x80) * 0x40 + c - 0x80 elseif 0xf0 <= a and a <= 0xf7 and b >= 0x80 and c >= 0x80 and d >= 0x80 then value = (((a - 0xf0) * 0x40 + b - 0x80) * 0x40 + c - 0x80) * 0x40 + d - 0x80 else return "" end if value <= 0xffff then return strformat ("\\u%.4x", value) elseif value <= 0x10ffff then -- encode as UTF-16 surrogate pair value = value - 0x10000 local highsur, lowsur = 0xD800 + floor (value/0x400), 0xDC00 + (value % 0x400) return strformat ("\\u%.4x\\u%.4x", highsur, lowsur) else return "" end end local isspecial = {} local unifile = io.open("UnicodeData.txt") if unifile then -- -- each line consists of 15 parts for each defined codepoints local pat = {} for i = 1,14 do pat[i] = "[^;]*;" end pat[1] = "([^;]*);" -- Codepoint pat[3] = "([^;]*);" -- Category pat[15] = "[^;]*" pat = table.concat(pat) for line in unifile:lines() do local cp, cat = line:match(pat) if cat:match("^C[^so]") or cat:match("^Z[lp]") then isspecial[tonumber(cp, 16)] = cat end end unifile:close() end local x,xe local t = {} local esc = {} local escerr = {} local range if opt == "esc_full" then range = 0x10ffff elseif opt == "esc_asc" then range = 0x7f else range = 0xffff end for i = 0,range do t[1] = unichar(i) xe = encode(t) x = string.match(xe, "^%s*%[%s*%\"(.*)%\"%s*%]%s*$") if type(x) ~= 'string' then escerr[i] = xe elseif string.lower(x) == escapeutf8(t[1]) then esc[i] = 'u' elseif x == escapecodes[t[1]] then esc[i] = 'c' elseif x:sub(1,1) == "\\" then escerr[i] = xe end end do local i = 0 while i <= range do local first while i <= range and not (esc[i] or isspecial[i]) do i = i + 1 end if i > range then break end first = i local special = isspecial[i] if esc[i] and special then while esc[i] and isspecial[i] == special do i = i + 1 end if i-1 > first then print (("Escaped %s characters from U+%04X to U+%04X"):format(special,first,i-1)) else print (("Escaped %s character U+%04X"):format(special,first)) end elseif esc[i] then while esc[i] and not isspecial[i] do i = i + 1 end if i-1 > first then print (("Escaped from U+%04X to U+%04X"):format(first,i-1)) else if first >= 32 and first <= 127 then print (("Escaped U+%04X (%c)"):format(first,first)) else print (("Escaped U+%04X"):format(first)) end end elseif special then while not esc[i] and isspecial[i] == special do i = i + 1 end if i-1 > first then print (("Unescaped %s characters from U+%04X to U+%04X"):format(special,first,i-1)) else print (("Unescaped %s character U+%04X"):format(special,first)) end end end end do local i = 0 while i <= range do local first while i <= range and not escerr[i] do i = i + 1 end if not escerr[i] then break end first = i while escerr[i] do i = i + 1 end if i-1 > first then print (("Errors while escaping from U+%04X to U+%04X"):format(first, i-1)) else print (("Errors while escaping U+%04X"):format(first)) end end end end -- Copyright (C) 2011 David Heiko Kolf -- -- Permission is hereby granted, free of charge, to any person obtaining -- a copy of this software and associated documentation files (the -- "Software"), to deal in the Software without restriction, including -- without limitation the rights to use, copy, modify, merge, publish, -- distribute, sublicense, and/or sell copies of the Software, and to -- permit persons to whom the Software is furnished to do so, subject to -- the following conditions: -- -- The above copyright notice and this permission notice shall be -- included in all copies or substantial portions of the Software. -- -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -- BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -- ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -- SOFTWARE.