OpenResty-CJSON工具类

一、前言:为什么不能直接用 cjson.encode/decode

在 OpenResty 中处理 JSON 时,很多开发者直接这样写:

Lua 复制代码
local cjson = require "cjson"
local data = cjson.decode(ngx.var.body)  -- 危险!

但这样做存在严重隐患:

  • 解析失败导致 worker 崩溃(如传入非法 JSON)
  • 中文乱码(未指定编码)
  • 空值处理不一致nullnil 导致字段丢失)
  • 循环引用死循环

解决方案:封装一个安全、健壮、易用的 CJSON 工具类!

本文将带你实现一个生产级 JSON 工具模块,彻底解决上述问题。


二、核心设计原则

我们的 json_util.lua 应具备:

✅ 使用 cjson.safe 避免崩溃

✅ 统一错误返回格式

✅ 支持空值保留(nullcjson.null

✅ 自动处理中文编码

✅ 提供快捷方法(如 parse, stringify


三、完整工具类实现

创建文件:/usr/local/openresty/lualib/lib/json_util.lua

Lua 复制代码
-- lib/json_util.lua
local cjson_safe = require "cjson.safe"

-- 配置全局行为
cjson_safe.encode_empty_table_as_object(false)  -- {} → []
cjson_safe.encode_sparse_array(true, 100, 10)   -- 容忍稀疏数组

local _M = {}

--- 安全解析 JSON 字符串
-- @param str string: JSON 字符串
-- @return table|nil, string|nil: 解析结果, 错误信息
function _M.parse(str)
    if type(str) ~= "string" or str == "" then
        return nil, "input is not a valid string"
    end

    local data, err = cjson_safe.decode(str)
    if not data then
        return nil, "JSON decode failed: " .. tostring(err)
    end

    return data, nil
end

--- 安全序列化 Lua 对象为 JSON
-- @param obj any: Lua 对象(table, number, string...)
-- @return string|nil, string|nil: JSON 字符串, 错误信息
function _M.stringify(obj)
    local json_str, err = cjson_safe.encode(obj)
    if not json_str then
        return nil, "JSON encode failed: " .. tostring(err)
    end
    return json_str, nil
end

--- 深度复制并过滤不可序列化字段(可选)
function _M.safe_stringify(obj)
    local function safe_copy(t)
        if type(t) ~= "table" then return t end
        local new_t = {}
        for k, v in pairs(t) do
            if type(v) == "table" then
                new_t[k] = safe_copy(v)
            elseif type(v) == "function" or type(v) == "thread" or type(v) == "userdata" then
                -- 跳过不可序列化类型
            else
                new_t[k] = v
            end
        end
        return new_t
    end

    return _M.stringify(safe_copy(obj))
end

--- 快捷方法:解析后自动填充默认值
function _M.parse_with_defaults(str, defaults)
    local data, err = _M.parse(str)
    if err then return nil, err end

    if type(defaults) == "table" then
        for k, v in pairs(defaults) do
            if data[k] == nil then
                data[k] = v
            end
        end
    end

    return data, nil
end

return _M

四、使用示例

4.1 安全解析请求体(推荐!)

Lua 复制代码
location /api {
    content_by_lua_block {
        local json_util = require "lib.json_util"
        ngx.req.read_body()
        local body = ngx.req.get_body_data()

        local req_data, err = json_util.parse(body)
        if err then
            ngx.status = 400
            ngx.say('{"error":"', err, '"}')
            return
        end

        -- 处理业务逻辑
        ngx.say("User ID: ", req_data.userId or "unknown")
    }
}

4.2 返回结构化 JSON 响应

Lua 复制代码
local response = {
    code = 200,
    data = { name = "Alice", age = 30 },
    timestamp = ngx.now()
}

local json_str, err = json_util.stringify(response)
if err then
    ngx.log(ngx.ERR, "Response serialize failed: ", err)
    ngx.exit(500)
end

ngx.say(json_str)

4.3 带默认值的解析(防字段缺失)

Lua 复制代码
local defaults = { page = 1, size = 10 }
local params, err = json_util.parse_with_defaults(body, defaults)
-- 若 body 中无 page,则 params.page = 1

五、关键特性详解

✅ 1. 使用 cjson.safe 防崩溃

  • 原生 cjson.decode 在非法 JSON 时会抛出 Lua error,导致 worker 退出
  • cjson.safe 返回 nil + error,可安全捕获

✅ 2. 空值(null)处理

  • 默认情况下,JSON 的 null 会被转为 Lua 的 nil,导致字段消失
  • 如需保留 null,可在 decode 后处理,或使用 cjson.null 标记

✅ 3. 中文支持

  • OpenResty 默认使用 UTF-8,只要客户端发送 UTF-8 JSON,无需额外处理
  • 确保 Content-Type: application/json; charset=utf-8

✅ 4. 循环引用防护

  • cjson.safe 内置深度限制(默认 1000 层),避免死循环

六、生产环境配置建议

nginx.conf 中设置合理的 JSON 限制:

Lua 复制代码
http {
    # 限制请求体大小(防 OOM)
    client_max_body_size 1m;

    # Lua 共享字典(可选:缓存常用 JSON 模板)
    lua_shared_dict json_cache 10m;
}

七、常见错误与避坑指南

问题 原因 解决方案
attempt to index a nil value JSON 字段不存在且未判空 使用 req_data.field or default
中文乱码 客户端未用 UTF-8 编码 要求客户端设置正确 charset
大数字精度丢失 Lua number 是 double 启用 cjson.decode_big_numbers_as_strings(true)
数组变对象 空 table {} 被序列化为 {} 调用 encode_empty_table_as_object(false)

💡 大数字处理示例

Lua 复制代码
cjson_safe.decode_big_numbers_as_strings(true)
local data = cjson_safe.decode('{"id": 12345678901234567890}')
-- data.id = "12345678901234567890" (string)

八、性能提示

  • cjson 是 C 实现,比纯 Lua JSON 库快 10~50 倍
  • ✅ 单次 encode/decode 耗时通常 < 0.1ms
  • ✅ 避免在循环中反复序列化大对象

九、结语

感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!

相关推荐
難釋懷8 小时前
OpenResty封装http工具
http·junit·openresty
he___H8 小时前
Nginx+lua+openresty
nginx·lua·openresty
難釋懷2 天前
OpenResty查询Tomcat
tomcat·firefox·openresty
難釋懷2 天前
OpenResty监听请求
junit·openresty
難釋懷2 天前
OpenResty获取参数的API
junit·openresty
難釋懷3 天前
OpenResty请求参数处理
junit·openresty
難釋懷3 天前
OpenResty获取参数并返回
junit·openresty
Boop_wu3 天前
[Java EE 进阶] Spring Boot 日志全面解析 : 配置与实战
junit·java-ee·单元测试
難釋懷6 天前
OpenResty快速入门
junit·openresty