Skip to content

json — JSON 编解码 ​

模块名:json

Lua 值与 JSON 字符串互转,基于 cjson。这是必加载模块,不需要在脚本里做任何声明。

只有两个函数,都只接受一个参数:多余参数会被忽略,缺参数会报错。

函数总览 ​

函数作用
json.encodeLua 值 → JSON 字符串
json.decodeJSON 字符串 → Lua 值

其它字段 ​

字段类型说明
json.nulllightuserdata表示 JSON null 的哨兵值。解码时 JSON 的 null 变成它;编码时把它写进表里会输出 null
json._NAMEstring"json"
json._VERSIONstring"2.1.0"

固件里 cjson 的配置函数(encode_max_depth、encode_sparse_array 等)都被注释掉了,不可调用。编译期固定值:编码/解码最大嵌套深度 1000,数字精度 14 位有效数字,编码时不允许 inf/nan,解码时允许。


json.encode ​

lua
json.encode(value) --> string

把 Lua 值编码为 JSON 字符串。

参数

#参数类型必填默认值说明
1valueany是—要编码的值。table / number / string / boolean / json.null

返回值

类型说明
stringJSON 字符串

示例

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 值。

参数

#参数类型必填默认值说明
1strstring是—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 包住:

    lua
    local ok, t = pcall(json.decode, payload)
    if not ok then print("JSON 格式错误") return end
  • JSON 里的 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

验证方法:

  1. 直接下载运行,日志应出现 out table,随后是一段带缩进的 1 = 1.1 / 2 = 2.2 / key = NULL 之类的列表。
  2. 把 sjson 换成自己云端下发的真实报文,就能用这张表核对字段名、看 null 落在了哪里。
  3. 用上面的修正版跑同一个 tjson,多个子表的键名应全部正确显示(原版的缺陷就在这一条上暴露)。

注意事项:

  • pairs 遍历顺序不确定,两次运行的输出行序可能不一样,别拿它当 JSON 原始顺序看。
  • 把 PrintTable 拷进自己的程序时忘了它依赖全局 key,与自己的业务变量重名会意外串数据。
  • 表里出现 json.null 时 tostring(v) 打出来不是 nil,容易误判「这个字段有值」;判断请用 v == json.null(见例程 3)。

LPPLC 工业物联网云平台 · 软硬件与云平台全栈自研