-- json.lua: High-performance JSON module for Moonshark local json = {} function json.go_encode(value) return __json_marshal(value) end function json.go_decode(str) if type(str) ~= "string" then error("json.decode: expected string, got " .. type(str), 2) end return __json_unmarshal(str) end function json.encode(data) local t = type(data) if t == "nil" then return "null" end if t == "boolean" then return data and "true" or "false" end if t == "number" then return tostring(data) end if t == "string" then local escape_chars = { ['"'] = '\\"', ['\\'] = '\\\\', ['\n'] = '\\n', ['\r'] = '\\r', ['\t'] = '\\t' } return '"' .. data:gsub('[\\"\n\r\t]', escape_chars) .. '"' end if t == "table" then local isArray = true local count = 0 local max_index = 0 for k, _ in pairs(data) do count = count + 1 if type(k) == "number" and k > 0 and math.floor(k) == k then max_index = math.max(max_index, k) else isArray = false break end end local result = {} if isArray then for i, v in ipairs(data) do result[i] = json.encode(v) end return "[" .. table.concat(result, ",") .. "]" else local size = 0 for k, v in pairs(data) do if type(k) == "string" and type(v) ~= "function" and type(v) ~= "userdata" then size = size + 1 end end result = {} local index = 1 for k, v in pairs(data) do if type(k) == "string" and type(v) ~= "function" and type(v) ~= "userdata" then result[index] = json.encode(k) .. ":" .. json.encode(v) index = index + 1 end end return "{" .. table.concat(result, ",") .. "}" end end return "null" -- Unsupported type end function json.decode(data) local pos = 1 local len = #data -- Pre-compute byte values local b_space = string.byte(' ') local b_tab = string.byte('\t') local b_cr = string.byte('\r') local b_lf = string.byte('\n') local b_quote = string.byte('"') local b_backslash = string.byte('\\') local b_slash = string.byte('/') local b_lcurly = string.byte('{') local b_rcurly = string.byte('}') local b_lbracket = string.byte('[') local b_rbracket = string.byte(']') local b_colon = string.byte(':') local b_comma = string.byte(',') local b_0 = string.byte('0') local b_9 = string.byte('9') local b_minus = string.byte('-') local b_plus = string.byte('+') local b_dot = string.byte('.') local b_e = string.byte('e') local b_E = string.byte('E') -- Skip whitespace more efficiently local function skip() local b while pos <= len do b = data:byte(pos) if b > b_space or (b ~= b_space and b ~= b_tab and b ~= b_cr and b ~= b_lf) then break end pos = pos + 1 end end -- Forward declarations local parse_value, parse_string, parse_number, parse_object, parse_array -- Parse a string more efficiently parse_string = function() pos = pos + 1 -- Skip opening quote if pos > len then error("Unterminated string") end -- Use a table to build the string local result = {} local result_pos = 1 local start = pos local c, b while pos <= len do b = data:byte(pos) if b == b_backslash then -- Add the chunk before the escape character if pos > start then result[result_pos] = data:sub(start, pos - 1) result_pos = result_pos + 1 end pos = pos + 1 if pos > len then error("Unterminated string escape") end c = data:byte(pos) if c == b_quote then result[result_pos] = '"' elseif c == b_backslash then result[result_pos] = '\\' elseif c == b_slash then result[result_pos] = '/' elseif c == string.byte('b') then result[result_pos] = '\b' elseif c == string.byte('f') then result[result_pos] = '\f' elseif c == string.byte('n') then result[result_pos] = '\n' elseif c == string.byte('r') then result[result_pos] = '\r' elseif c == string.byte('t') then result[result_pos] = '\t' else result[result_pos] = data:sub(pos, pos) end result_pos = result_pos + 1 pos = pos + 1 start = pos elseif b == b_quote then -- Add the final chunk if pos > start then result[result_pos] = data:sub(start, pos - 1) result_pos = result_pos + 1 end pos = pos + 1 return table.concat(result) else pos = pos + 1 end end error("Unterminated string") end -- Parse a number more efficiently parse_number = function() local start = pos local b = data:byte(pos) -- Skip any sign if b == b_minus then pos = pos + 1 if pos > len then error("Malformed number") end b = data:byte(pos) end -- Integer part if b < b_0 or b > b_9 then error("Malformed number") end repeat pos = pos + 1 if pos > len then break end b = data:byte(pos) until b < b_0 or b > b_9 -- Fractional part if pos <= len and b == b_dot then pos = pos + 1 if pos > len or data:byte(pos) < b_0 or data:byte(pos) > b_9 then error("Malformed number") end repeat pos = pos + 1 if pos > len then break end b = data:byte(pos) until b < b_0 or b > b_9 end -- Exponent if pos <= len and (b == b_e or b == b_E) then pos = pos + 1 if pos > len then error("Malformed number") end b = data:byte(pos) if b == b_plus or b == b_minus then pos = pos + 1 if pos > len then error("Malformed number") end b = data:byte(pos) end if b < b_0 or b > b_9 then error("Malformed number") end repeat pos = pos + 1 if pos > len then break end b = data:byte(pos) until b < b_0 or b > b_9 end return tonumber(data:sub(start, pos - 1)) end -- Parse an object more efficiently parse_object = function() pos = pos + 1 -- Skip opening brace local obj = {} skip() if pos <= len and data:byte(pos) == b_rcurly then pos = pos + 1 return obj end while pos <= len do skip() if data:byte(pos) ~= b_quote then error("Expected string key") end local key = parse_string() skip() if data:byte(pos) ~= b_colon then error("Expected colon") end pos = pos + 1 obj[key] = parse_value() skip() local b = data:byte(pos) if b == b_rcurly then pos = pos + 1 return obj end if b ~= b_comma then error("Expected comma or closing brace") end pos = pos + 1 end error("Unterminated object") end -- Parse an array more efficiently parse_array = function() pos = pos + 1 -- Skip opening bracket local arr = {} local index = 1 skip() if pos <= len and data:byte(pos) == b_rbracket then pos = pos + 1 return arr end while pos <= len do arr[index] = parse_value() index = index + 1 skip() local b = data:byte(pos) if b == b_rbracket then pos = pos + 1 return arr end if b ~= b_comma then error("Expected comma or closing bracket") end pos = pos + 1 end error("Unterminated array") end -- Parse a value more efficiently parse_value = function() skip() if pos > len then error("Unexpected end of input") end local b = data:byte(pos) if b == b_quote then return parse_string() elseif b == b_lcurly then return parse_object() elseif b == b_lbracket then return parse_array() elseif b == string.byte('n') and pos + 3 <= len and data:sub(pos, pos + 3) == "null" then pos = pos + 4 return nil elseif b == string.byte('t') and pos + 3 <= len and data:sub(pos, pos + 3) == "true" then pos = pos + 4 return true elseif b == string.byte('f') and pos + 4 <= len and data:sub(pos, pos + 4) == "false" then pos = pos + 5 return false elseif b == b_minus or (b >= b_0 and b <= b_9) then return parse_number() else error("Unexpected character: " .. string.char(b)) end end skip() local result = parse_value() skip() if pos <= len then error("Unexpected trailing characters") end return result end function json.is_valid(str) if type(str) ~= "string" then return false end local status, _ = pcall(json.decode, str) return status end function json.pretty_print(value) if type(value) == "string" then value = json.decode(value) end local function stringify(val, indent, visited) visited = visited or {} indent = indent or 0 local spaces = string.rep(" ", indent) if type(val) == "table" then if visited[val] then return "{...}" end visited[val] = true local isArray = true local i = 1 for k in pairs(val) do if type(k) ~= "number" or k ~= i then isArray = false break end i = i + 1 end local result = isArray and "[\n" or "{\n" local first = true if isArray then for i, v in ipairs(val) do if not first then result = result .. ",\n" end first = false result = result .. spaces .. " " .. stringify(v, indent + 1, visited) end else for k, v in pairs(val) do if not first then result = result .. ",\n" end first = false result = result .. spaces .. " \"" .. tostring(k) .. "\": " .. stringify(v, indent + 1, visited) end end return result .. "\n" .. spaces .. (isArray and "]" or "}") elseif type(val) == "string" then return "\"" .. val:gsub('\\', '\\\\'):gsub('"', '\\"'):gsub('\n', '\\n') .. "\"" else return tostring(val) end end return stringify(value) end return json