-- JSON Encoder/Decoder for LSP server -- Minimal implementation supporting basic JSON parsing and encoding local json = {} --- Check if a table is an array (sequential numeric keys starting from 1) --- @param t table Table to check --- @return boolean True if table is an array local function is_array(t) local i = 0 for _ in pairs(t) do i = i + 1 if t[i] == nil then return false end end return true end --- Escape special characters in string for JSON --- @param s string String to escape --- @return string Escaped string local function escape(s) s = s:gsub("\\", "\\\\") s = s:gsub('"', '\\"') s = s:gsub("\n", "\\n") s = s:gsub("\r", "\\r") s = s:gsub("\t", "\\t") return s end --- Encode Lua value to JSON string --- @param data any Value to encode --- @return string JSON string function json.encode(data) local t = type(data) if t == "nil" then return "null" elseif t == "boolean" then return data and "true" or "false" elseif t == "number" then return tostring(data) elseif t == "string" then return '"' .. escape(data) .. '"' elseif t == "table" then local parts = {} if is_array(data) then for _, v in ipairs(data) do table.insert(parts, json.encode(v)) end return "[" .. table.concat(parts, ",") .. "]" else for k, v in pairs(data) do table.insert(parts, '"' .. escape(k) .. '":' .. json.encode(v)) end return "{" .. table.concat(parts, ",") .. "}" end else return "null" end end --- Skip whitespace in string --- @param s string JSON string --- @param i number Current position --- @return number New position after whitespace local function skip_whitespace(s, i) while i <= #s and s:sub(i, i):match("%s") do i = i + 1 end return i end --- Parse JSON string literal --- @param s string JSON string --- @param i number Current position (at opening quote) --- @return string|nil Parsed string --- @return number New position local function parse_string(s, i) local result = {} i = i + 1 while i <= #s do local c = s:sub(i, i) if c == '"' then return table.concat(result), i + 1 elseif c == "\\" and i < #s then local next_c = s:sub(i + 1, i + 1) if next_c == "n" then table.insert(result, "\n") elseif next_c == "r" then table.insert(result, "\r") elseif next_c == "t" then table.insert(result, "\t") elseif next_c == '"' then table.insert(result, '"') elseif next_c == "\\" then table.insert(result, "\\") else table.insert(result, next_c) end i = i + 2 else table.insert(result, c) i = i + 1 end end return nil, i end --- Parse JSON number --- @param s string JSON string --- @param i number Current position --- @return number Parsed number --- @return number New position local function parse_number(s, i) local start = i if s:sub(i, i) == "-" then i = i + 1 end while i <= #s and s:sub(i, i):match("%d") do i = i + 1 end if s:sub(i, i) == "." then i = i + 1 while i <= #s and s:sub(i, i):match("%d") do i = i + 1 end end if s:sub(i, i):match("[eE]") then i = i + 1 if s:sub(i, i):match("[+-]") then i = i + 1 end while i <= #s and s:sub(i, i):match("%d") do i = i + 1 end end return tonumber(s:sub(start, i - 1)), i end local parse_value --- Parse JSON array --- @param s string JSON string --- @param i number Current position (at opening bracket) --- @return table|nil Parsed array --- @return number New position local function parse_array(s, i) local arr = {} i = i + 1 while i <= #s do i = skip_whitespace(s, i) if s:sub(i, i) == "]" then return arr, i + 1 end local val val, i = parse_value(s, i) table.insert(arr, val) i = skip_whitespace(s, i) if s:sub(i, i) == "]" then return arr, i + 1 end if s:sub(i, i) == "," then i = i + 1 end end return nil, i end --- Parse JSON object --- @param s string JSON string --- @param i number Current position (at opening brace) --- @return table|nil Parsed object --- @return number New position local function parse_object(s, i) local obj = {} i = i + 1 while i <= #s do i = skip_whitespace(s, i) if s:sub(i, i) == "}" then return obj, i + 1 end if s:sub(i, i) == '"' then local key key, i = parse_string(s, i) i = skip_whitespace(s, i) if s:sub(i, i) ~= ":" then return nil, i end i = i + 1 local val val, i = parse_value(s, i) obj[key] = val i = skip_whitespace(s, i) if s:sub(i, i) == "}" then return obj, i + 1 end if s:sub(i, i) == "," then i = i + 1 end else return nil, i end end return nil, i end parse_value = function(s, i) i = skip_whitespace(s, i) if i > #s then return nil, i end local c = s:sub(i, i) if c == "{" then return parse_object(s, i) elseif c == "[" then return parse_array(s, i) elseif c == '"' then return parse_string(s, i) elseif c == "t" then if s:sub(i, i + 3) == "true" then return true, i + 4 end return nil, i elseif c == "f" then if s:sub(i, i + 4) == "false" then return false, i + 5 end return nil, i elseif c == "n" then if s:sub(i, i + 3) == "null" then return nil, i + 4 end return nil, i elseif c == "-" or c:match("%d") then return parse_number(s, i) else return nil, i end end --- Decode JSON string to Lua value --- @param s string JSON string --- @return any|nil Parsed value --- @return number|nil Position after parsing or error function json.decode(s) if type(s) ~= "string" then return nil, "invalid input" end local data, i = parse_value(s, 1) return data, i end return json