如何在 Redis 中使用 cmsgpack 和 Lua 存储具有空值的 JSON?

How to store a JSON with null values with cmsgpack and Lua in Redis?

我一直在阅读有关 cjson 的文档,并了解将 null 值转换为 cjson.null 值(因为无法在 Lua tables 中存储 nil . 如果我使用 cjson.decode 和 cjson.encode.

效果很好

但是,当我尝试用 cmsgpack, the keys with null values are not present. I use code similar to this one 打包和解压 table 的内容时。

你是怎么解决这个问题的?您是否将空值替换为特殊值({isnull: true} 在调用 cmspack.pack 之前,然后将 {isnull: true} 替换回 null)或使用任何其他技术?

我进行了快速研究,但没有找到任何处理散列 table 中的 null 值的 msgpack 库。不过,我并不是说这样的库不存在。

我选择了另一种方法。由于 lua-cmsgpack 在解码 MessagePack 二进制数据时没有对处理 null 值做任何特殊处理,而 nil 值仅在 tables 中有问题,我们可以添加一些特殊情况代码对于哈希 → table 解码器。

实际上,补丁非常简单:

--- a/lua_cmsgpack.c
+++ b/lua_cmsgpack.c
@@ -565,6 +565,10 @@ void mp_decode_to_lua_hash(lua_State *L, mp_cur *c, size_t len) {
         mp_decode_to_lua_type(L,c); /* key */
         if (c->err) return;
         mp_decode_to_lua_type(L,c); /* value */
+        if (lua_isnil(L, -1)) {
+            lua_pop(L, 1);
+            lua_pushlightuserdata(L, NULL);
+        }
         if (c->err) return;
         lua_settable(L,-3);
     }

这里第二次调用mp_decode_to_lua_type将一个hash条目value转换为对应的Lua类型值,当值为nil(即MessagePack中的null),我们将其替换为轻量用户数据(NULL指针)。相同的用户数据(NULL 指针)用作 cjson.null 值。

我们不需要修补编码器,因为它会将任何不受支持的值(包括任何用户数据)编码为 null,也就是说,您可以使用 cjson.null 用户数据来表示 null 值(cjson.decode 就是这样做的)。解码哈希后,您将获得相同的用户数据。

同样的技巧可以应用于类似数组的 table,但如果您知道 table 的大小则没有必要 — 您可以从 table 迭代从第一个到最后一个索引。

local cjson = require('cjson')
local cmsgpack = require('cmsgpack')

local encoded_hash = cmsgpack.pack({k1 = nil, k2 = cjson.null, k3 = 'baz'})
local decoded_hash = cmsgpack.unpack(encoded_hash)
print('decoded_hash.k2 == cjson.null?', decoded_hash.k2 == cjson.null)
for k, v in pairs(decoded_hash) do
    print(k, '->', v)
end

local encoded_array = cmsgpack.pack({'foo', nil, cjson.null, 'bar'})
local decoded_array = cmsgpack.unpack(encoded_array)
print('#decoded_array =', #decoded_array)
for i = 1, 4 do
    print(i, '->', decoded_array[i])
end

输出:

decoded_hash.k2 == cjson.null?  true
k3  ->  baz
k2  ->  userdata: (nil)
#decoded_array =    4
1   ->  foo
2   ->  nil
3   ->  userdata: (nil)
4   ->  bar

感谢 LuaRocks,我们甚至不需要为这样一个微不足道的补丁支持库的分支。补丁可以直接包含在 rockspec 文件中。我用修改过的 rockspec 创建了一个要点:https://gist.github.com/un-def/d6b97f5ef6b47edda7f65e0c6144663c

您可以使用以下命令安装补丁版本的库:

luarocks install https://gist.github.com/un-def/d6b97f5ef6b47edda7f65e0c6144663c/raw/8fe2f1ad70c75bfe2532a1f4cf9213f4e28423d3/lua-cmsgpack-0.4.0-0.rockspec