跳到主要内容

Moon中的Lua Json Lib

Lua的json库主要是对table进行encode和decode, 先列举以下lua和json的差异

  1. Lua中只有一种数据结构table, 而json的定义是区分数组和对象的
  2. Lua中table的key可以是integer类型, 而json对象的key只能是string类型

对于第一个差异, 主要在于table没有一个明确的标识来区分它是array还是hash,这就需要做一些推断。

下面结合常见的lua-json库来说明这点:

local rapidjson = require("rapidjson")
local cjson = require("cjson")

local integer_key_table1 = {
[1] = "a",
[2] = "b",
[100] = "c",
}

local integer_key_table2 = {
[101] = "a",
[102] = "b",
[103] = "c",
}

print("rapidjson outout1:", rapidjson.encode(integer_key_table1))
print("cjson outout1:", pcall(cjson.encode,integer_key_table1))

print("rapidjson outout2:", rapidjson.encode(integer_key_table2))
print("cjson outout2:", pcall(cjson.encode,integer_key_table2))

--[[
rapidjson outout1: ["a","b"]
cjson outout1: false Cannot serialise table: excessively sparse array
rapidjson outout2: {}
cjson outout2: false Cannot serialise table: excessively sparse array
]]

可见对于这种情况,rapidjson和cjson库表现都不好。moon中的json库解决了这个问题:采用了少量代价,来检测它是array还是hash

static inline size_t array_size(lua_State* L, int index)
{
// test first key
lua_pushnil(L);
if (lua_next(L, index) == 0) // empty table
return 0;

lua_Integer firstkey = lua_isinteger(L, -2) ? lua_tointeger(L, -2) : 0;
lua_pop(L, 2);

if (firstkey <= 0)
{
return 0;
}
else if (firstkey == 1)
{
/*
* https://www.lua.org/manual/5.4/manual.html#3.4.7
* The length operator applied on a table returns a border in that table.
* A border in a table t is any natural number that satisfies the following condition :
* (border == 0 or t[border] ~= nil) and t[border + 1] == nil
*/
auto len = (lua_Integer)lua_rawlen(L, index);
lua_pushinteger(L, len);
if (lua_next(L, index)) // has more fields?
{
lua_pop(L, 2);
return 0;
}
return len;
}

auto len = (lua_Integer)lua_rawlen(L, index);
if (firstkey > len)
return 0;

lua_pushnil(L);
while (lua_next(L, index) != 0)
{
if (lua_isinteger(L, -2))
{
lua_Integer x = lua_tointeger(L, -2);
if (x > 0 && x <= len)
{
lua_pop(L, 1);
continue;
}
}
lua_pop(L, 2);
return 0;
}
return len;
}

结果测试:

local json = require("json")

local integer_key_table = {
[1] = "a",
[2] = "b",
[100] = "c",
}

print("moonjson outout:", json.encode(integer_key_table))
--- moonjson outout: {"1":"a","2":"b","100":"c"}

这里就解决了encode无法区分array还是hash的问题。 但decode时又有另一个问题:Json的key都是string类型,造成decode 后原本的lua integer-key 变成 string-key, 我使用了如下方案解决了这个问题:

lua变量不能是 -,0-9 开头,json key decode时,先检测第一个字符是否是-,0-9 开头,如果是,就说明这是一个整型key。

local json = require("json")

local integer_key_table = {
[1] = "a",
[2] = "b",
[100] = "c",
}

local str = json.encode(integer_key_table)
print_r(json.decode(str))
--[[
{
[1] = "a",
[2] = "b",
[100] = "c",
}
]]

这种方案有一些限制:

decode时无法区分{["1"]="a",["2"]="b"}这种格式