local M = {} function M.json_decode(json) if type(json) ~= "string" then return nil, "input must be a string" end local pos = 1 local len = #json local function skip_ws() while pos <= len and json:sub(pos,pos):match("%s") do pos = pos + 1 end end local function parse_value() skip_ws() local c = json:sub(pos,pos) if c == "{" then return parse_object() elseif c == "[" then return parse_array() elseif c == '"' then return parse_string() elseif c:match("[%d%-]") then return parse_number() elseif json:sub(pos,pos+3) == "true" then pos=pos+4; return true elseif json:sub(pos,pos+4) == "false" then pos=pos+5; return false elseif json:sub(pos,pos+3) == "null" then pos=pos+4; return nil else return nil, "invalid value at position "..pos end end function parse_string() pos = pos + 1 local start_pos = pos local str = "" while pos <= len do local c = json:sub(pos,pos) if c == '"' then str = str .. json:sub(start_pos,pos-1) pos = pos + 1 return str elseif c == "\\" then str = str .. json:sub(start_pos,pos-1) pos = pos + 1 local esc = json:sub(pos,pos) local map = {b="\b", f="\f", n="\n", r="\r", t="\t", ['"']='"', ["\\"]="\\", ["/"]="/"} str = str .. (map[esc] or esc) pos = pos + 1 start_pos = pos else pos = pos + 1 end end return nil, "unterminated string" end function parse_number() local start_pos = pos while pos <= len and json:sub(pos,pos):match("[%d%+%-eE%.]") do pos = pos + 1 end local n = tonumber(json:sub(start_pos,pos-1)) if not n then return nil, "invalid number at position "..start_pos end return n end function parse_array() pos = pos + 1 local arr = {} skip_ws() if json:sub(pos,pos) == "]" then pos=pos+1; return arr end while true do local val, err = parse_value() if err then return nil, err end table.insert(arr,val) skip_ws() local c = json:sub(pos,pos) if c == "]" then pos=pos+1; break elseif c == "," then pos=pos+1 else return nil, "expected ',' or ']' at position "..pos end end return arr end function parse_object() pos = pos + 1 local obj = {} skip_ws() if json:sub(pos,pos) == "}" then pos=pos+1; return obj end while true do skip_ws() if json:sub(pos,pos) ~= '"' then return nil, "expected string key at "..pos end local key, err = parse_string() if err then return nil, err end skip_ws() if json:sub(pos,pos) ~= ":" then return nil, "expected ':' at "..pos end pos = pos + 1 local val, err = parse_value() if err then return nil, err end obj[key] = val skip_ws() local c = json:sub(pos,pos) if c == "}" then pos=pos+1; break elseif c == "," then pos=pos+1 else return nil, "expected ',' or '}' at "..pos end end return obj end local result, err = parse_value() if err then return nil, err end skip_ws() if pos <= len then return nil, "trailing characters at "..pos end return result end function M.json_encode(value) local t = type(value) if t == "nil" then return "null" elseif t == "boolean" then return tostring(value) elseif t == "number" then return tostring(value) elseif t == "string" then return '"' .. value:gsub('[%z\1-\31\\"]', { ['\\'] = '\\\\', ['"'] = '\\"', ['\b'] = '\\b', ['\f'] = '\\f', ['\n'] = '\\n', ['\r'] = '\\r', ['\t'] = '\\t' }):gsub("[%z\1-\31]", function(c) return string.format("\\u%04x", c:byte()) end) .. '"' elseif t == "table" then local is_array = true local max_index = 0 for k,v in pairs(value) do if type(k) ~= "number" then is_array = false else if k > max_index then max_index = k end end end local items = {} if is_array then for i = 1, max_index do table.insert(items, M.json_encode(value[i])) end return "[" .. table.concat(items,",") .. "]" else for k,v in pairs(value) do if type(k) ~= "string" then return nil, "object keys must be strings" end table.insert(items, M.json_encode(k) .. ":" .. M.json_encode(v)) end return "{" .. table.concat(items,",") .. "}" end else return nil, "unsupported type: " .. t end end return M