Skip to content
Bruce edited this page Oct 1, 2023 · 4 revisions

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"}这种格式