local M = {}
M.NULL = setmetatable({}, { __tostring = function() return "null" end })
local decode_value
local function skip_ws(s, i)
while i <= #s do
local c = s:byte(i)
if c == 32 or c == 9 or c == 10 or c == 13 then
i = i + 1
else
return i
end
end
return i
end
local function codepoint_to_utf8(code)
if code < 0x80 then
return string.char(code)
elseif code < 0x800 then
return string.char(
0xC0 + (code >> 6),
0x80 + (code & 0x3F))
elseif code < 0x10000 then
return string.char(
0xE0 + (code >> 12),
0x80 + ((code >> 6) & 0x3F),
0x80 + (code & 0x3F))
else
return string.char(
0xF0 + (code >> 18),
0x80 + ((code >> 12) & 0x3F),
0x80 + ((code >> 6) & 0x3F),
0x80 + (code & 0x3F))
end
end
local function decode_string(s, i)
local out = {}
i = i + 1
while i <= #s do
local c = s:sub(i, i)
if c == '"' then
return table.concat(out), i + 1
elseif c == "\\" then
local nxt = s:sub(i + 1, i + 1)
if nxt == '"' or nxt == "\\" or nxt == "/" then
out[#out + 1] = nxt
i = i + 2
elseif nxt == "n" then out[#out + 1] = "\n"; i = i + 2
elseif nxt == "t" then out[#out + 1] = "\t"; i = i + 2
elseif nxt == "r" then out[#out + 1] = "\r"; i = i + 2
elseif nxt == "b" then out[#out + 1] = "\b"; i = i + 2
elseif nxt == "f" then out[#out + 1] = "\f"; i = i + 2
elseif nxt == "u" then
local hex = s:sub(i + 2, i + 5)
if #hex ~= 4 or not hex:match("^%x%x%x%x$") then
error(string.format(
"json: bad \\u escape at byte %d", i), 0)
end
local code = tonumber(hex, 16)
if code >= 0xD800 and code <= 0xDBFF then
if s:sub(i + 6, i + 7) ~= "\\u" then
error(string.format(
"json: unpaired high surrogate at byte %d", i), 0)
end
local lo_hex = s:sub(i + 8, i + 11)
if #lo_hex ~= 4 or not lo_hex:match("^%x%x%x%x$") then
error(string.format(
"json: bad low surrogate at byte %d", i + 6), 0)
end
local lo = tonumber(lo_hex, 16)
code = 0x10000 + ((code - 0xD800) << 10) + (lo - 0xDC00)
i = i + 12
else
i = i + 6
end
out[#out + 1] = codepoint_to_utf8(code)
else
error(string.format(
"json: bad escape \\%s at byte %d", nxt, i), 0)
end
else
out[#out + 1] = c
i = i + 1
end
end
error("json: unterminated string", 0)
end
local function decode_number(s, i)
local j = i
if s:sub(j, j) == "-" then j = j + 1 end
while j <= #s and s:sub(j, j):match("[%d.eE+-]") do j = j + 1 end
local n = tonumber(s:sub(i, j - 1))
if not n then
error(string.format("json: bad number at byte %d", i), 0)
end
return n, j
end
local function decode_array(s, i)
local arr = {}
i = skip_ws(s, i + 1)
if s:sub(i, i) == "]" then return arr, i + 1 end
while i <= #s do
local v
v, i = decode_value(s, i)
arr[#arr + 1] = v
i = skip_ws(s, i)
local c = s:sub(i, i)
if c == "," then
i = skip_ws(s, i + 1)
elseif c == "]" then
return arr, i + 1
else
error(string.format(
"json: expected ',' or ']' at byte %d, got %q", i, c), 0)
end
end
error("json: unterminated array", 0)
end
local function decode_object(s, i)
local obj = {}
i = skip_ws(s, i + 1)
if s:sub(i, i) == "}" then return obj, i + 1 end
while i <= #s do
if s:sub(i, i) ~= '"' then
error(string.format(
"json: expected string key at byte %d", i), 0)
end
local key
key, i = decode_string(s, i)
i = skip_ws(s, i)
if s:sub(i, i) ~= ":" then
error(string.format(
"json: expected ':' after key at byte %d", i), 0)
end
i = skip_ws(s, i + 1)
local val
val, i = decode_value(s, i)
obj[key] = val
i = skip_ws(s, i)
local c = s:sub(i, i)
if c == "," then
i = skip_ws(s, i + 1)
elseif c == "}" then
return obj, i + 1
else
error(string.format(
"json: expected ',' or '}' at byte %d, got %q", i, c), 0)
end
end
error("json: unterminated object", 0)
end
decode_value = function(s, i)
i = skip_ws(s, i)
local c = s:sub(i, i)
if c == '"' then return decode_string(s, i)
elseif c == "{" then return decode_object(s, i)
elseif c == "[" then return decode_array(s, i)
elseif c == "t" then
if s:sub(i, i + 3) == "true" then return true, i + 4 end
error(string.format("json: expected 'true' at byte %d", i), 0)
elseif c == "f" then
if s:sub(i, i + 4) == "false" then return false, i + 5 end
error(string.format("json: expected 'false' at byte %d", i), 0)
elseif c == "n" then
if s:sub(i, i + 3) == "null" then return M.NULL, i + 4 end
error(string.format("json: expected 'null' at byte %d", i), 0)
elseif c == "-" or (c >= "0" and c <= "9") then
return decode_number(s, i)
else
error(string.format(
"json: unexpected char %q at byte %d", c, i), 0)
end
end
function M.decode(s)
if type(s) ~= "string" then
error("json.decode: input must be string", 0)
end
if #s == 0 then
error("json.decode: empty input", 0)
end
local v, i = decode_value(s, 1)
i = skip_ws(s, i)
if i <= #s then
error(string.format(
"json.decode: trailing garbage at byte %d", i), 0)
end
return v
end
return M