serialize
serialize
function serialize( value ) --> str
Description
It serializes the value. The resulting string can be parsed by the common Lua load/loadstring function to restore the original value. It have not the Lua literal limitation for tables, as the one found in the lualiteral function. So it can handle tables with cycles or with a nest level higher than the max defined for the Lua literals (200). It still can not handle userdata and lightuserdata.
Parameters
- value
-
The value to be serialized.
Return Values
- str
-
A string that can be read back from common load/loadstring function to recontruct the original data.
Code
--ZFUNC-lualiteral-v0 local function lualiteral( value, alias ) --> str --TODO -- export ??? local function atomdump( val ) local t = type( val ) if "nil" == t then return 'nil' elseif "number" == t then return tostring( val ) elseif "boolean" == t then return tostring( val ) elseif "string" ~= t then val = tostring( val ) end return string.format( "%q", val ) end local function lualiteral_rec( cur, alias, result ) if "table" ~= type( cur ) then table.insert( result, atomdump( cur ) ) else -- If a table alias is defined, use that name local substitute if alias then substitute = alias[ cur ] end if substitute then table.insert( result, substitute ) else -- Recurse over each key and each value table.insert( result, "{" ) for k, v in pairs( cur ) do table.insert( result, "[" ) lualiteral_rec( k, alias, result ) table.insert( result, "]=" ) lualiteral_rec( v, alias, result ) table.insert( result, "," ) end table.insert( result, "}" ) end end return result end local result = lualiteral_rec( value, alias, {} ) return table.concat( result ) end local function stored_name( s ) if not s then return "P" end return "P["..s.."]" end local function table_cut_point_rec( tab, acc ) if not acc then acc = { alias = {}, stored = 0, refcount = {}, depth = 1 } end if "table" == type( tab ) then -- Skip if already marked as a cut point if acc.alias[ tab ] then return acc end -- Update reference counter and nest level local count = acc.refcount[ tab ] if not count then count = 0 end count = count + 1 acc.refcount[ tab ] = count local depth = acc.depth + 1 if count < 2 -- No multiple reference to this table and depth < 151 -- Below the maximum nest level allowed by lua (200) then acc.depth = depth else -- Marsk as cut point local store_number = acc.stored + 1 acc.alias[ tab ] = stored_name( store_number ) acc.stored = store_number acc.depth = 1 end -- Recurse over each key and each value for key, value in pairs( tab ) do table_cut_point_rec( key, acc ) table_cut_point_rec( value, acc ) end acc.depth = depth - 1 end return acc end --ZFUNC-serialize-v0 local function serialize( value ) --> str --TODO local result = {} local acc = table_cut_point_rec( value ) -- Fallback to single table literal, if possible if acc.stored < 1 then return "return "..lualiteral( value, acc.alias ) end -- Append the alias name definition table.insert( result, "local "..stored_name( ).."={}" ) acc.stored = acc.stored + 1 acc.alias[ value ] = stored_name( acc.stored ) table.insert( result, ";for i=1,"..acc.stored.." do "..stored_name().."[i]={}end" ) -- Place the right keys in each alias for tab, alias in pairs( acc.alias ) do for key, value in pairs( tab ) do table.insert( result, ";"..alias.."[" ) table.insert( result, lualiteral(key, acc.alias )) table.insert( result, "]=" ) table.insert( result, lualiteral(value, acc.alias )) end end -- Return the alias of the root table table.insert( result, ';return '..stored_name(acc.stored )) return table.concat( result ) end return serialize
Examples
local t = require "taptest" local serialize = require "serialize" -- TODO : export as luazdf function local function deepcompare( a, b, c, s ) if not s then s = {} end if type( a ) ~= type( b ) then return false end if type( a ) ~= "table" then if c then return c( a, b ) end return a == b end if s[ a ] == b then return true end s[ a ] = b for k, v in pairs( a ) do local o for bk in pairs( b ) do if deepcompare( k, bk, c, s ) then o = b[ bk ] break end end if not o then return false end if not deepcompare( v, o, c, s ) then return false end end return true end local function reco( v ) if loadstring then return loadstring( serialize( v ) )() else return load( serialize( v ) )() end end -- Simple values t( reco( nil ), nil, deepcompare ) t( reco( true ), true, deepcompare ) t( reco( 1 ), 1, deepcompare ) t( reco( "hi" ), "hi", deepcompare ) t( reco( {} ), {}, deepcompare ) -- Table with values t( reco( { a = true, b = { "c", 1, { d = "e" } }, } ), { a = true, b = { "c", 1, { d = "e" } }, }, deepcompare ) -- Table table key t( reco( { [ { ok = "ok" } ] = "ok", } ), { [ { ok = "ok" } ] = "ok", }, deepcompare ) -- Table with reference local atab = { a = "a" } t( reco( { atab, a = atab, } ), { atab, a = atab, }, deepcompare ) -- Table with cycle atab = { a = {} } atab.a.a = atab t( reco( atab ), atab, deepcompare ) -- Too deep table local cur = atab for n = 1, 200 do cur.q = {} cur = cur.q end t( reco( atab ), atab, deepcompare ) t()