Lua序列化

我们经常需要序列化一些数据,为了将数据转换为字节流或者字符流,这样我们就可以保存到文件或者通过网络发送出去。我们可以在 Lua 代码中描述序列化的数据,在这种方式下,我们运行读取程序即可从代码中构造出保存的值。

number/string

对于number和string类型其实非常好序列化,number类型就直接返回其本身即可:

Lua 复制代码
if type(o) == "number" then
    return o
end

string类型需要专门处理,有一个正则匹配是%q,是一种字符串格式化表达式,为了防止类似os.execute('rm *')的注入式攻击,所以采用这种匹配形式来完成string的序列化:

Lua 复制代码
if type(o) == "number" then
    return string.format("%q",o)
end

若采用类似于("[[", o, "]]")的形式来实现序列化,那么如果输入是" ]]..os.execute('rm *')..[[ ",最后拼接结果则是[[ ]]..os.execute('rm *')..[[ ]],load之后则会出现严重的后果。

table

Lua 复制代码
{
        ["b"] = "Lua",
        ["a"] = 12,
        ["key"] = {
                ["b"] = 4,
                ["a"] = 3,
        },
        1 = 2,
}

如果是table类型,如果按照以上形式来呈现序列化效果,则需要注意嵌套table和缩进格式。

Lua 复制代码
function serialize(o,strPrefix)
    strPrefix = strPrefix or ""
    if type(o) == "number" then
        return o
    elseif type(o) == "string" then
        return string.format("%q",o)
    elseif type(o) == "table" then
        local result = "{\n"
        for k,v in pairs(o) do
            local strFormat = "%s"
            if type(k) == "string" then
                strFormat = "[\"%s\"]"
            end
            result = result..strPrefix.."\t"..string.format(strFormat,k).." = "..serialize(v,strPrefix.."\t")..",\n"
        end
        result = result..strPrefix.."}"
        return result
    else 
        error("cannot serialize a " .. type(o)) 
    end
end

通过pairs循环来对key value值一个个序列化,其中strFormat指的是如果key的类型是string才需要加上【】,以确保正确序列化key值。如果是嵌套table则需要注意递归序列化,传入这个新的table和缩进用以保证正常的序列化输出。于是输入和输出如下:

Lua 复制代码
local tb = 
{
    ["a"] = 12, 
    ["b"] = "Lua", 
    [1] = 2,
    ["key"] = {["a"] = 3,["b"] = 4,} 
}
print(serialize(tb))
Lua 复制代码
{
        ["b"] = "Lua",
        ["a"] = 12,
        ["key"] = {
                ["b"] = 4,
                ["a"] = 3,
        },
        [1] = 2,
}

如果看programming in lua原文,其实大差不差,只是原文没有缩进格式的优化。

循环table

如果出现循环引用table的形式,那么整个问题将会变得比较复杂一点,比如:

Lua 复制代码
local a = {}
a.c = 1
a.z = a

由此可以在之前的基础上做一个优化:缓存table map。意指当我们遍历里面的key值,发现其中仍然有序列化之前已经被序列化的table,则做特殊处理:

Lua 复制代码
local tableMap = {} --用以保存已经被序列化的table
local tbKeyToTableSerialize = {} --用以保存那些引用了table本身的key的序列化string

function serialize(o,strPrefix)
    strPrefix = strPrefix or "" --string前缀,用来正确显示缩进
    if type(o) == "number" then
        return o
    elseif type(o) == "string" then
        return string.format("%q",o)
    elseif type(o) == "table" then
        local result = "{\n"
        --如果自身是table则第一时间加入表中
        tableMap[o] = o.name

        for k,v in pairs(o) do
            local strFormat = "[%s]"
            if type(k) == "string" then
                strFormat = "[\"%s\"]"
            end
            if type(v) == "table" then
                --如果是tablemap没有的,则正常序列化,并且存进map
                if not tableMap[v] then
                    result = result..strPrefix.."\t"..string.format(strFormat,k).." = "..serialize(v,strPrefix.."\t")..",\n"
                else
                    --否则直接添加到序列化string中,之后直接输出即可
                    table.insert(tbKeyToTableSerialize,o.name.."."..k.." = "..tableMap[v])
                end
            else
                result = result..strPrefix.."\t"..string.format(strFormat,k).." = "..serialize(v,strPrefix.."\t")..",\n"
            end
        end
        result = result..strPrefix.."}"
        return result
    else 
        error("cannot serialize a " .. type(o)) 
    end
end

function serializeAll(o)
    print(o.name.." = "..serialize(o))
    for i = 1,#tbKeyToTableSerialize do
        print(tbKeyToTableSerialize[i])
    end
    tbKeyToTableSerialize = {}
    tableMap = {}
end

最终测试代码和测试结果:

Lua 复制代码
local a = {}
a.name = "a"
a.z = a
a.x = {}
a.x.name = "a.x"
a.x.y = a
a.x.x = a.x
serializeAll(a)
Lua 复制代码
a = {
        ["x"] = {
                ["name"] = "a.x",
        },
        ["name"] = "a",
}
a.z = a
a.x.y = a
a.x.x = a.x

当然这里的name只是我这边显式添加的value值,实际上可以setmetatable里面复写index来达成条件,但依然是不知道具体table名的。

官方文档相比以上会更激进一些,其直接换成了另一个输出格式:

Lua 复制代码
a = {} 
a[1] = {} 
a[1][1] = "one"
a[1][2] = "two"
a[2] = 3 
b = {} 
b["k"] = a[1] 
相关推荐
童先生15 分钟前
Go 项目中实现类似 Java Shiro 的权限控制中间件?
开发语言·go
lulu_gh_yu16 分钟前
数据结构之排序补充
c语言·开发语言·数据结构·c++·学习·算法·排序算法
Re.不晚40 分钟前
Java入门15——抽象类
java·开发语言·学习·算法·intellij-idea
老秦包你会42 分钟前
Qt第三课 ----------容器类控件
开发语言·qt
凤枭香1 小时前
Python OpenCV 傅里叶变换
开发语言·图像处理·python·opencv
ULTRA??1 小时前
C加加中的结构化绑定(解包,折叠展开)
开发语言·c++
远望清一色1 小时前
基于MATLAB的实现垃圾分类Matlab源码
开发语言·matlab
confiself1 小时前
大模型系列——LLAMA-O1 复刻代码解读
java·开发语言
XiaoLeisj1 小时前
【JavaEE初阶 — 多线程】Thread类的方法&线程生命周期
java·开发语言·java-ee
杜杜的man1 小时前
【go从零单排】go中的结构体struct和method
开发语言·后端·golang