主题
json — JSON 编解码
模块名:json
Lua 值与 JSON 字符串互转,基于 cjson。这是必加载模块,不需要在脚本里做任何声明。
只有两个函数,都只接受一个参数:多余参数会被忽略,缺参数会报错。
函数总览
| 函数 | 作用 |
|---|---|
json.encode | Lua 值 → JSON 字符串 |
json.decode | JSON 字符串 → Lua 值 |
其它字段
| 字段 | 类型 | 说明 |
|---|---|---|
json.null | lightuserdata | 表示 JSON null 的哨兵值。解码时 JSON 的 null 变成它;编码时把它写进表里会输出 null |
json._NAME | string | "json" |
json._VERSION | string | "2.1.0" |
固件里 cjson 的配置函数(encode_max_depth、encode_sparse_array 等)都被注释掉了,不可调用。编译期固定值:编码/解码最大嵌套深度 1000,数字精度 14 位有效数字,编码时不允许 inf/nan,解码时允许。
json.encode
lua
json.encode(value) --> string把 Lua 值编码为 JSON 字符串。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | value | any | 是 | — | 要编码的值。table / number / string / boolean / json.null |
返回值
| 类型 | 说明 |
|---|---|
| string | JSON 字符串 |
示例
lua
print(json.encode({ name = "LPPLC", temp = 25.6, ok = true }))
-- {"name":"LPPLC","temp":25.6,"ok":true}
print(json.encode({ 1, 2, 3 }))
-- [1,2,3]说明
- Lua 的 table 既可能是数组也可能是字典。键从 1 开始连续的整数键会编码成 JSON 数组,其它情况编码成对象。
- 空表
{}会被编码成{}(对象),不是[]。需要保证输出空数组时,请在服务端容错,或塞一个占位元素。 - 数字精度 14 位有效数字,超长的浮点会被截断。
- 编码
inf/nan会报错,浮点计算结果先用if v ~= v之类判断一下更稳。 - 出错时是抛异常(不是返回 nil),外部数据参与编码时建议
pcall。
实用例程
lua
--开启mc协议服务器,端口为5000
mcs=mc_server.start(5000,4,1024)
--读取电表
state=data.creat(0)
dlt=com_dlt645.creat('<设备ID或凭据>',state)--建立电表驱动参数为表号12位表号2007广播表号
kwh=data.creat(0)
kw=data.creat(0)
dlt:add_channel(com_dlt645.KW_2007,kw);--读取功率
dlt:add_channel(com_dlt645.KWH_2007,kwh);--读取正向有功总电量到D0
com1=com.setup(1,2400,com.EVEN,8,1)--打开串口父设备2007表默认2400BPS
com1:add_device(dlt)--在串口父设备中加入电表子设备
com1:start()--启动串口父设备
local 倍率=1
dssc=timer.start(5000,function()--启动定时器 5 秒发布一个数据
local kwhreal=kwh:get_num()*倍率
--local kwhreal = string.format("%.2f",kwh:get_num()*倍率)
local jsontable =
{
name="<名称>",
kw=kw:get_num()*倍率,
kwh = kwhreal,
}
plc.df(200,kwhreal)--把电能浮点值保存到D200
local jsonstring = json.encode(jsontable)
--print(jsonstring)
print("<名称>",plc.df(200),"<名称>",plc.dd(204))
end
)把上面的地址、主题、引脚替换成你项目里的即可。
json.decode
lua
json.decode(str) --> value把 JSON 字符串解码为 Lua 值。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | str | string | 是 | — | JSON 字符串 |
返回值
| 类型 | 说明 |
|---|---|
| any | 解码结果。JSON 对象/数组 → table;null → json.null |
示例
lua
local t = json.decode('{"temp":25.6,"ok":true,"list":[1,2,3]}')
print(t.temp, t.ok, t.list[2]) -- 25.6 true 2说明
解析失败会抛异常,处理外部输入(MQTT payload、HTTP 响应、串口数据)必须用
pcall包住:lualocal ok, t = pcall(json.decode, payload) if not ok then print("JSON 格式错误") return endJSON 里的
null解码成json.null,它不等于 Lua 的 nil。判空要写if t.field == json.null then,或者if t.field == nil or t.field == json.null then。
实用例程
lua
return string.format("%04X",ret);
end
--程序运行
system.sleep(200)
while plc.bite(plc.ds(5),7)==0 do
system.sleep(100)
end
send_T=0 --发送时间
client=tcp_client.creat(plc.dstr(300),Unsigned(plc.d(308))) --网口建立连接服务器对象
client:set_timeout(3000)
client:reg(tcp_client.RECVDATA,function(rec_data) --注册接收数据回调函数
if type(rec_data)~="<设备ID或凭据>" then
return
end
rec_json = json.decode(rec_data)
if type(rec_json)=="table" then
-- print( rec_json["dssn"], IDbox , rec_json["<设备ID或凭据>"] , plc.dsd(37))
if rec_json["dssn"] == IDbox and rec_json["<设备ID或凭据>"] == plc.dsd(37) then
Resend=0
--print(Resend)
end
end
end
)
client:reg(tcp_client.CONNECTED,function() --注册连接成功回调函数
--print("连接服务器成功")
plc.m(1,1) --执行发送
plc.d(60,2) --2连接服务器成功
end
)
client:reg(tcp_client.DISCONNECTED,function() --注册断开连接回调函数把上面的地址、主题、引脚替换成你项目里的即可。
常见用法
安全解析下发的控制指令
lua
mqtt.reg(mqttc, mqtt.RECVDATA, function(topic, payload)
local ok, cmd = pcall(json.decode, payload)
if not ok or type(cmd) ~= "table" then
print("非法 JSON:", payload)
return
end
if type(cmd.setpoint) == "number" then
plc.d(200, math.floor(cmd.setpoint))
end
if cmd.y0 ~= nil and cmd.y0 ~= json.null then
plc.y(0, cmd.y0 and 1 or 0)
end
end)组装上报报文
lua
timer.start(5000, function()
local body = json.encode({
device = plc.plc_name(),
ts = os.time(),
temp = plc.df(100),
run = plc.m(0) == 1,
})
mqtt.publish(mqttc, "data/up", body, 0, 0)
end)注意事项
json.decode失败会抛异常,处理外部输入一律用pcall。回调里未捕获的异常会导致整个脚本重启。null解码成json.null,不是nil。- 空表编码成
{}而不是[]。 - 只有
encode/decode两个函数,cjson 的各种配置接口在本固件里不可用。 - 解码大 JSON 会一次性构建大 table,ESP32 内存有限,上报/下发的报文尽量保持精简。
例程
统一模板「场景 / 完整代码 / 要点 / 验证方法 / 注意事项」。
例程 1:安全解析下发 JSON 指令(pcall)
适用:LP3201 / LP3202 / LP3203 / LP3222 / LP3232 / LP3288 / 软PLC模拟器 / 上位机 | 依赖模块:json mqtt plc | 难度:★☆☆
场景:云端下发 { "setpoint": 60, "y0": true },解析后写寄存器/输出。
完整代码(可直接下载运行):
lua
mqtt.reg(mqttc, mqtt.RECVDATA, function(topic, payload)
local ok, cmd = pcall(json.decode, payload)
if not ok or type(cmd) ~= "table" then
print("非法 JSON:", payload)
return
end
if type(cmd.setpoint) == "number" then
plc.d(200, math.floor(cmd.setpoint))
end
if cmd.y0 ~= nil and cmd.y0 ~= json.null then
plc.y(0, cmd.y0 and 1 or 0)
end
end)要点:json.decode 失败抛异常,外部输入一律用 pcall 包住;未捕获异常会让整个脚本重启。
验证方法:下发合法 JSON 后 D200/Y0 变化;下发乱码只打印"非法 JSON"。
注意事项:JSON null 解码成 json.null(不是 nil),判空要 == json.null 或 == nil。
例程 2:组装上报报文
适用:同上 | 依赖模块:json mqtt timer plc | 难度:★☆☆
场景:定时把设备名、时间戳、温度、运行状态打包成 JSON 上报。
完整代码(可直接下载运行):
lua
timer.start(5000, function()
local body = json.encode({
device = plc.plc_name(),
ts = os.time(),
temp = plc.df(100),
run = plc.m(0) == 1,
})
mqtt.publish(mqttc, "data/up", body, 0, 0)
end)要点:键从 1 开始连续整数键编码为数组;空表 {} 编码成 {}(对象)不是 []。
验证方法:云端收到 { "device":"LP...", "ts":..., "temp":25.6, "run":true }。
注意事项:字段值是 inf/nan 会编码报错,浮点结果先判 v == v(nan≠nan)。
例程 3:注意事项——null 与空表
适用:同上 | 依赖模块:json | 难度:★☆☆
完整代码(可直接下载运行):
lua
local t = json.decode('{"a":null}')
print(t.a == nil) -- false(是 json.null 不是 nil)
print(t.a == json.null) -- true
print(json.encode({})) -- {}(对象)
print(json.encode({1,2,3})) -- [1,2,3](数组)要点:null → json.null;空表默认编成对象。
验证方法:上述打印分别为 false / true / {} / [1,2,3]。
注意事项:需要空数组时服务端容错,或塞占位元素;只有 encode/decode 两个函数,cjson 配置接口不可用。
例程 4:现场常用脚本 json.LPLUA(递归打印 + 长字符串写 JSON,原文)
适用:同上 | 依赖模块:json string | 难度:★★☆
场景:拿到一段 JSON 字符串后想看看解出来的表长什么样。这份脚本自带一个递归打印函数 PrintTable,是 JSON 调试用得最多的一份(文件 json.LPLUA),原文收录如下。
完整代码(json.LPLUA 原文):
lua
key = ""
function PrintTable(table , level)
level = level or 1
local indent = ""
for i = 1, level do
indent = indent.." "
end
if key ~= "" then
print(indent..key.." ".."=".." ".."{")
else
print(indent .. "{")
end
key = ""
for k,v in pairs(table) do
if type(v) == "table" then
key = k
PrintTable(v, level + 1)
else
local content = string.format("%s%s = %s", indent .. " ",tostring(k), tostring(v))
print(content)
end
end
print(indent .. "}")
end
--初始化一个table
local torigin =
{
KEY6 = {key=1.1,1.2,1.3,5,6.8989898123456789},
}
function test()
end
--一个函数对象是不能转到JSON的,但为容错,值为null
--local torigin={1.1,2.2,3.3,key=test}
--转JSON
--local sjson = json.encode(torigin)
--输出JSON
--print("testJson.encode",sjson)
local sjson=[[{"1":1.1,"2":2.2,"3":3.3,"key":null}]]
local tjson=json.decode(sjson)
print("out",type(tjson))
PrintTable(tjson,2)要点:
[[ ... ]]长字符串用来包 JSON 文本,里面的双引号不用转义 —— 写带引号的 JSON 时用这个最省事(跟例程 3 的单引号写法等价,但 JSON 里本身全是双引号时长字符串更好读)。print("out", type(tjson))打印table,这行的作用是在遍历前先确认decode真的拿到了表。json.decode出来的就是一张普通 Lua 表,所以能直接用pairs递归遍历 —— 这是这份脚本真正想演示的事。- ⚠
PrintTable用全局变量key传递子表的键名,而一进循环又立刻key = "":一个表里有两个以上子表时,后面的子表会把key覆盖掉、只有一个能带出键名,其余只显示成{。当调试用的「缩进打印器」没问题,不要当正确的层级标签工具用。 torigin里的{key=1.1, 1.2, 1.3, 5, 6.8989898123456789}是混合表(既有数组部分又有哈希部分),json.encode这类表的结果取决于实现;另外这么长的小数在 double 里只有约 15~17 位有效十进制数字,再长会被舍。- 注释那句「一个函数对象不能转到 JSON,但为容错值为 null」是作者留下的说明:表里放函数时编码不会崩,对应位置给 null。
只修掉 key 缺陷的最小改动(保持原输出风格):
lua
function PrintTable(t, level, kname)
level = level or 1
local indent = string.rep(" ", level)
print(indent .. (kname and (kname .. " = {") or "{"))
for k, v in pairs(t) do
if type(v) == "table" then
PrintTable(v, level + 1, tostring(k)) -- 键名随参数传,不用全局
else
print(indent .. " " .. tostring(k) .. " = " .. tostring(v))
end
end
print(indent .. "}")
end验证方法:
- 直接下载运行,日志应出现
out table,随后是一段带缩进的1 = 1.1/2 = 2.2/key = NULL之类的列表。 - 把
sjson换成自己云端下发的真实报文,就能用这张表核对字段名、看null落在了哪里。 - 用上面的修正版跑同一个
tjson,多个子表的键名应全部正确显示(原版的缺陷就在这一条上暴露)。
注意事项:
pairs遍历顺序不确定,两次运行的输出行序可能不一样,别拿它当 JSON 原始顺序看。- 把
PrintTable拷进自己的程序时忘了它依赖全局key,与自己的业务变量重名会意外串数据。 - 表里出现
json.null时tostring(v)打出来不是nil,容易误判「这个字段有值」;判断请用v == json.null(见例程 3)。