lua序列化(支持循环引用)

lua序列化

  • 支持key类型为string, number
  • 支持value类型为string, number, table
  • 支持循环引用
  • 支持加密序列化
  • 支持loadstring反序列化

使用示例

local t = { a = 1, b = 2}
local g = { c = 3, d = 4,  t}
t.rt = g
local ser_str = ser(g)
local unser_table = loadstring(sered)()

原理详解

采用递归序列化表的方式实现,并且支持循环引用,实现思路参考了云风的序列化,从云风那里学到了不少东西。
先说不考虑支持循环引用的简单情况,采用递归方式实现序列化很简单

直接使用type函数分别判断表中key-value对儿分别是什么数据类型,然后分别处理

序列化key

local keystr = nil
if type(k) == "string" then
    keystr = string.format("[\"%s\"]", k)
elseif type(k) == "number" then
    keystr = string.format("[%d]", k)
end

序列化value

local valuestr = nil
if type(v) == "string" then
    valuestr = string.format("\"%s\"", tostring(v))
elseif type(v) == "number" then
    valuestr = tostring(v)
elseif type(v) == "table" then
    valuestr = table_ser(v, fullkey, mark, assign)
end

分别处理完key和value直接插入一个表容器中就可以,最后在使用table.concat连接字符串就可以,这里要说一下lua中拼接字符串是个比较低效的行为,这跟lua字符串实现有关,每拼接都会重新生成一个新串,所以字符串越长拼接会越慢
这里使用table.concat,不但减少了拼接次数,而且这样拼接效率比较高,因为是调用C拼接,而非Lua字符串的行为

return string.format("{%s}", table.concat(container, ","))

精简后的代码

local function table_ser(tablevalue)
    -- 记录表中各项
    local container = {}
    for k, v in pairs(tablevalue) do
        -- 序列化key
        local keystr = nil
        if type(k) == "string" then
            keystr = string.format("[\"%s\"]", k)
        elseif type(k) == "number" then
            keystr = string.format("[%d]", k)
        end

        -- 序列化value
        local valuestr = nil
        if type(v) == "string" then
            valuestr = string.format("\"%s\"", tostring(v))
        elseif type(v) == "number" then
            valuestr = tostring(v)
        elseif type(v) == "table" then
            valuestr = table_ser(v, fullkey, mark, assign)
        end

        table.insert(container, string.format("%s=%s", keystr, valuestr))
    end
    return string.format("{%s}", table.concat(container, ","))
end

支持循环引用

其实普通序列化没什么好说的,重点在于对循环引用的支持思路参考了云风的实现

使用一个表mark记录所有序列化过的表,并记录其全key(从根表到当前表的全路径key)每次新序列化一个表时,首先查看是否已经序列化过,若没有序列化则直接序列化, 若已经序列化过则处理如下:
在一个表assgin中记录所有循环引用情况,并给出正确赋值(因为循环引用不能直接序列化,
所以间接的在表构造之后赋值),最后可以一起拼接到一起。

table序列化实现如下:

local function table_ser(tablevalue, tablekey, mark, assign)
    -- 标记当前table, 并记录其key名
    mark[tablevalue] = tablekey
    -- 记录表中各项
    local container = {}
    for k, v in pairs(tablevalue) do
        -- 序列化key
        local keystr = nil
        if type(k) == "string" then
            keystr = string.format("[\"%s\"]", k)
        elseif type(k) == "number" then
            keystr = string.format("[%d]", k)
        end

        -- 序列化value
        local valuestr = nil
        if type(v) == "string" then
            valuestr = string.format("\"%s\"", tostring(v))
        elseif type(v) == "number" then
            valuestr = tostring(v)
        elseif type(v) == "table" then
            -- 获得从根表到当前表项的完整key, tablekey(代表tablevalue的key), mark[v]代表table v的key
            local fullkey = string.format("%s%s", tablekey, keystr)
            if mark[v] then table.insert(assign, string.format("%s=%s", fullkey, mark[v]))
            else valuestr = table_ser(v, fullkey, mark, assign)
            end
        end

        if keystr and valuestr then
            local keyvaluestr = string.format("%s=%s", keystr, valuestr)
            table.insert(container, keyvaluestr)
        end
    end
    return string.format("{%s}", table.concat(container, ","))
end

调用table的序列化

local function ser(var, enc)
    assert(type(var)=="table")
    -- 标记所有出现的table, 并记录其key, 用于处理循环引用
    local mark = {}
    -- 用于记录循环引用的赋值语句
    local assign = {}
    -- 序列化表, ret字符串必须与后面的loca ret=%s中的ret相同,因为这个ret可能也会组织到结果字符串中。
    local ret = table_ser(var, "ret", mark, assign)
    local ret = string.format("local ret=%s %s return ret", ret, table.concat(assign, ";"))
    return (enc==nil or enc==true) and string.dump(loadstring(ret)) or ret
end

mark:处理循环引用最重要的就是mark表,它记录了已经序列化的表和其完整key路径
assgin: 记录循环引用的后期赋值语句,将这些语句拼接到表构造之外

序列化后就是一个lua的table创建并赋值的代码字符串,所以可以使用loadstring直接加载,加载后是一个chunk,可以当作函数运行就返回结果

序列化加密

string.dump(loadstring(ret))

这就是加密的代码,因为string.dump参数必须是function, 所以使用loadstring将字符串加载成chunk,然后在由string.dump导成字节码
其实就是使用了string.dump函数,它可以function导成二进制字节码,使用它处理一下就可以把明文字符串转成字节码了

完整代码见我github中的luaser

时间: 2024-10-08 16:41:28

lua序列化(支持循环引用)的相关文章

解决MVC Json序列化的循环引用问题/EF Json序列化循引用问题---Newtonsoft.Json

1..Net开源Json序列化工具Newtonsoft.Json中提供了解决序列化的循环引用问题: 方式1:指定Json序列化配置为 ReferenceLoopHandling.Ignore 方式2:指定 JsonIgnore忽略 引用对象 实例1,解决MVC的Json序列化引用方法: step1:在项目上添加引用 Newtonsoft.Json程序包,命令:Insert-Package Newtonsoft.Json step2:在项目中添加一个类,继承JsonResult,代码如下: ///

EntityFramework中Json序列化的循环引用问题解决--Newtonsoft.Json

1.在使用EF时,由于数据库主外键关联,将对象进行Json序列化时会遇到循环引用的问题 //EF 中由于数据库主外键关联,对象的序列化经常出现循环引用问题 //使用.Net 自带的序列化工具,序列化出现循环引用问题 List<student> list = _Context.students.ToList(); JavaScriptSerializer ser = new JavaScriptSerializer(); string str = ser.Serialize(list); Con

一个C#序列化时循环引用的问题

以前一直没搞懂为什么C#在做对象序列化时(Json序列化,XML序列化等)有时候会出现循环引用的问题,下面写了个例子,类People有一个属性引用了类Child,而类Child也有一个属性引用了类People,并且两个属性的get访问器中都会new一个彼此类型的对象,这样在访问People类的Child属性的时候就会new一个Child对象,在访问Child类的People属性的时候又会new一个People对象.所以C#序列化方法在序列化People类的Child属性时又会去序列化Child属

Wcf序列化的循环引用问题1

1.Wcf数据契约序列化,使用的类DataContractSerializer 默认如果类不指定[DataContract],则序列化类的所有字段,并且在出现循环引用的时候回抛出异常,服务终止 msdn文档说明:https://msdn.microsoft.com/library/system.runtime.serialization.datacontractserializer.aspx /* * Wcf 数据契约序列化使用“DataContractSerializer”,底层是xml序列化

Xml序列化自引用/循环引用问题2

1.类定义: public class Student { public int ID { get; set; } public string Name { get; set; } //[XmlIgnore] public Grade Grade { get; set; } } public class Grade { public int GradeID { get; set; } public string GradeName { get; set; } public List<Studen

Xml序列化自引用/循环引用问题1

1.定义类 public class Student { public int ID { get; set; } public string Name { get; set; } //[XmlIgnore] public Student StudentInfo { get; set; } } 2.执行序列化操作,如果内部对象引用自己,xml序列化抛出异常“检测到循环引用” Student stu = new Student(); stu.ID = 1; stu.Name = "张三";

解决.Net MVC EntityFramework Json 序列化循环引用问题.

以前都是到处看博客,今天小菜也做点贡献,希望能帮到大家. 废话不多说,直接进入正题. 用过.net MVC的同学应该都被json序列化报循环引用错误这个问题骚扰过.网上有一些解决办法,但是都治标不治本.如在引发异常的属性上加上[ScriptIgnore]或者[JsonIgnore],又或者用db.Configuration.ProxyCreationEnabled = false;这些解决办法都存在问题且需要多处修改并且测试.本小菜之前一直被其骚扰,就在前两天我决定一定要找到比较优的解决办法,g

Samples DataBind FastJson循环引用问题

Fastjson full support databind, it's simple to use. Encode import com.alibaba.fastjson.JSON; Group group = new Group(); group.setId(0L); group.setName("admin"); User guestUser = new User(); guestUser.setId(2L); guestUser.setName("guest"

spring源码系列——spring循环引用

众所周知spring在默认单例的情况下是支持循环引用的 Appconfig.java类的代码 @Configurable@ComponentScan("com.shadow")public class Appconfig {}1234X.java类的代码 package com.shadow.service; import org.springframework.beans.factory.annotation.Autowired;import org.springframework.