Table Serialization

lua-users home
wiki

Here are functions to serialize/unserialize a table or object (usually, not always, represented as a table), which is to convert it to and from a string representation. This is typically used for display (e.g. debugging) or storing data in a file (e.g. persistence).

Design decisions include

Because of these different needs, there have been many implementations.

Implementations

Listed people are implementions and descriptions of table persistence functions.

(Note: it would be useful to describe and compare these, clean them up, etc.)

Identity-preserving table serialization by Metalua

This example doesn't focus on the readability of the serialized table. Instead, and contrary to other examples in this page, it conserves shared sub-structures. Here's an example of a test that would be passed by metalua serialization, but not by pretty-printers:

> x={ 1 }

> x[2] = x

> x[x] = 3

> x[3]={ 'indirect recursion', [x]=x }

> y = { x, x }

> x.y = y

> assert (y[1] == y[2])

> s = serialize (x)

> z = loadstring (s)()

> assert (z.y[1] == z.y[2])

> =s

local _={ }

_[1]={ "indirect recursion" }

_[2]={ false, false }

_[3]={ 1, false, _[1], ["y"] = _[2] }

_[3][2] = _[3]

_[1][_[3]] = _[3]

_[3][_[3]] = 3

_[2][1] = _[3]

_[2][2] = _[3]

return _[3]

>

Sources for this serializer are available on the MetaLua repository: [2].

Metalua table.tostring and table.print

These functions are intended for pretty-printing rather than serialization: they don't preserve identity. They will terminate, though: if a table references itself, the inner occurrence will be printed as "[table: 0x12345678]" in order do avoid infinite recursion.

require "table2"

require "string2"



local u = {9}

local t = {2, "3\"4", {5, 6}, x=function() end, [u]=u}



table.print(t)

--> { [{ 9 }] = { 9 }, x = function: 0x6a2870, 2, "3\"4", { 5, 6 } }

table.print(t, 'nohash')

--> { 2, "3\"4", { 5, 6 } }

table.print(t, 'nohash', 10)

--> { 2,

--    "3\"4",

--    { 5, 6 } }



-- The `tag' field is particularly important in metalua, to represent tree-like structures.

-- As such, it has got a special syntax, introduced by a back-quote "`",

-- which is rendered by default by metalua's pretty printers.

local t = {tag='Sum', 1, {tag='Product', 2, 3}, lines={10,11}}



table.print(t)

--> `Sum{ lines = { 10, 11 }, 1, `Product{ 2, 3 } } -- metalua tag syntax

table.print(t, 'nohash')

--> `Sum{ 1, `Product{ 2, 3 } }

table.print(t, 'nohash', 10)  -- metalua tag syntax

--> `Sum{ 1,

--        `Product{ 2,

--                  3 } }



-- tags syntax can be disabled:

table.print(t, 'nohash', 'notag')

--> { tag = "Sum", 1, { tag = "Product", 2, 3 } }  -- tag syntax disabled



-- Note: table.print(t, ...) is equivalent to print(table.tostring(t, ...)).

Print a table recursively

ISSUE: this should return a string rather than assume how the user wants to output the text.

-- Print anything - including nested tables

function table_print (tt, indent, done)

  done = done or {}

  indent = indent or 0

  if type(tt) == "table" then

    for key, value in pairs (tt) do

      io.write(string.rep (" ", indent)) -- indent it

      if type (value) == "table" and not done [value] then

        done [value] = true

        io.write(string.format("[%s] => table\n", tostring (key)));

        io.write(string.rep (" ", indent+4)) -- indent it

        io.write("(\n");

        table_print (value, indent + 7, done)

        io.write(string.rep (" ", indent+4)) -- indent it

        io.write(")\n");

      else

        io.write(string.format("[%s] => %s\n",

            tostring (key), tostring(value)))

      end

    end

  else

    io.write(tt .. "\n")

  end

end

Universal tostring

function table_print (tt, indent, done)

  done = done or {}

  indent = indent or 0

  if type(tt) == "table" then

    local sb = {}

    for key, value in pairs (tt) do

      table.insert(sb, string.rep (" ", indent)) -- indent it

      if type (value) == "table" and not done [value] then

        done [value] = true

        table.insert(sb, "{\n");

        table.insert(sb, table_print (value, indent + 2, done))

        table.insert(sb, string.rep (" ", indent)) -- indent it

        table.insert(sb, "}\n");

      elseif "number" == type(key) then

        table.insert(sb, string.format("\"%s\"\n", tostring(value)))

      else

        table.insert(sb, string.format(

            "%s = \"%s\"\n", tostring (key), tostring(value)))

       end

    end

    return table.concat(sb)

  else

    return tt .. "\n"

  end

end



function to_string( tbl )

    if  "nil"       == type( tbl ) then

        return tostring(nil)

    elseif  "table" == type( tbl ) then

        return table_print(tbl)

    elseif  "string" == type( tbl ) then

        return tbl

    else

        return tostring(tbl)

    end

end

Example

print(to_string{

  "Lua",user="Mariacher",

  {{co=coroutine.create(function() end),{number=12345.6789}},

   func=function() end}, boolt=true} )

This prints

"Lua"

{

  {

    {

      number = "12345.6789"

    }

    co = "thread: 0212B848"

  }

  func = "function: 01FC7C70"

}

boolt = "true"

user = "Mariacher"

(the above code was originally from TableUtils)

PHP-like print_r

Based on [PHP print_r]. Based on code by Nick Gammon, hacked by DracoBlue to fit to [PHP print_r]-Style.

Example: print_r{ 5,3,{5,3} } -->


[1] => 5

[2] => 3

[3] => Table 

   {

     [1] => 5

     [2] => 3

   }

Compatibility: Lua 5.0 and 5.1

function print_r (t, indent, done)

  done = done or {}

  indent = indent or ''

  local nextIndent -- Storage for next indentation value

  for key, value in pairs (t) do

    if type (value) == "table" and not done [value] then

      nextIndent = nextIndent or

          (indent .. string.rep(' ',string.len(tostring (key))+2))

          -- Shortcut conditional allocation

      done [value] = true

      print (indent .. "[" .. tostring (key) .. "] => Table {");

      print  (nextIndent .. "{");

      print_r (value, nextIndent .. string.rep(' ',2), done)

      print  (nextIndent .. "}");

    else

      print  (indent .. "[" .. tostring (key) .. "] => " .. tostring (value).."")

    end

  end

end



function print_r (t, indent) -- alt version, abuse to http://richard.warburton.it

  local indent=indent or ''

  for key,value in pairs(t) do

    io.write(indent,'[',tostring(key),']') 

    if type(value)=="table" then io.write(':\n') print_r(value,indent..'\t')

    else io.write(' = ',tostring(value),'\n') end

  end

end



-- alt version2, handles cycles, functions, booleans, etc

--  - abuse to http://richard.warburton.it

-- output almost identical to print(table.show(t)) below.

function print_r (t, name, indent)

  local tableList = {}

  function table_r (t, name, indent, full)

    local serial=string.len(full) == 0 and name

        or type(name)~="number" and '["'..tostring(name)..'"]' or '['..name..']'

    io.write(indent,serial,' = ') 

    if type(t) == "table" then

      if tableList[t] ~= nil then io.write('{}; -- ',tableList[t],' (self reference)\n')

      else

        tableList[t]=full..serial

        if next(t) then -- Table not empty

          io.write('{\n')

          for key,value in pairs(t) do table_r(value,key,indent..'\t',full..serial) end 

          io.write(indent,'};\n')

        else io.write('{};\n') end

      end

    else io.write(type(t)~="number" and type(t)~="boolean" and '"'..tostring(t)..'"'

                  or tostring(t),';\n') end

  end

  table_r(t,name or '__unnamed__',indent or '','')

end

Here is a more complete version of print_r:

Sorry for the length!

--[[

   Author: Julio Manuel Fernandez-Diaz

   Date:   January 12, 2007

   (For Lua 5.1)

   

   Modified slightly by RiciLake to avoid the unnecessary table traversal in tablecount()



   Formats tables with cycles recursively to any depth.

   The output is returned as a string.

   References to other tables are shown as values.

   Self references are indicated.



   The string returned is "Lua code", which can be procesed

   (in the case in which indent is composed by spaces or "--").

   Userdata and function keys and values are shown as strings,

   which logically are exactly not equivalent to the original code.



   This routine can serve for pretty formating tables with

   proper indentations, apart from printing them:



      print(table.show(t, "t"))   -- a typical use

   

   Heavily based on "Saving tables with cycles", PIL2, p. 113.



   Arguments:

      t is the table.

      name is the name of the table (optional)

      indent is a first indentation (optional).

--]]

function table.show(t, name, indent)

   local cart     -- a container

   local autoref  -- for self references



   --[[ counts the number of elements in a table

   local function tablecount(t)

      local n = 0

      for _, _ in pairs(t) do n = n+1 end

      return n

   end

   ]]

   -- (RiciLake) returns true if the table is empty

   local function isemptytable(t) return next(t) == nil end



   local function basicSerialize (o)

      local so = tostring(o)

      if type(o) == "function" then

         local info = debug.getinfo(o, "S")

         -- info.name is nil because o is not a calling level

         if info.what == "C" then

            return string.format("%q", so .. ", C function")

         else 

            -- the information is defined through lines

            return string.format("%q", so .. ", defined in (" ..

                info.linedefined .. "-" .. info.lastlinedefined ..

                ")" .. info.source)

         end

      elseif type(o) == "number" or type(o) == "boolean" then

         return so

      else

         return string.format("%q", so)

      end

   end



   local function addtocart (value, name, indent, saved, field)

      indent = indent or ""

      saved = saved or {}

      field = field or name



      cart = cart .. indent .. field



      if type(value) ~= "table" then

         cart = cart .. " = " .. basicSerialize(value) .. ";\n"

      else

         if saved[value] then

            cart = cart .. " = {}; -- " .. saved[value] 

                        .. " (self reference)\n"

            autoref = autoref ..  name .. " = " .. saved[value] .. ";\n"

         else

            saved[value] = name

            --if tablecount(value) == 0 then

            if isemptytable(value) then

               cart = cart .. " = {};\n"

            else

               cart = cart .. " = {\n"

               for k, v in pairs(value) do

                  k = basicSerialize(k)

                  local fname = string.format("%s[%s]", name, k)

                  field = string.format("[%s]", k)

                  -- three spaces between levels

                  addtocart(v, fname, indent .. "   ", saved, field)

               end

               cart = cart .. indent .. "};\n"

            end

         end

      end

   end



   name = name or "__unnamed__"

   if type(t) ~= "table" then

      return name .. " = " .. basicSerialize(t)

   end

   cart, autoref = "", ""

   addtocart(t, name, indent)

   return cart .. autoref

end

A test:

-----------------------------------------------------------

--- testing table.show



t = {1, {2, 3, 4}, default = {"a", "b", d = {12, "w"}, e = 14}}

t.g = t.default



print("-----------------------------------")

print(table.show(t))                -- shows __unnamed__ table



tt = {1, h = {["p-q"] = "a", b = "e", c = {color = 3, name = "abc"}}, 2}



f = table.show

tt[f] = "OK"



print("-----------------------------------")

print(table.show(tt, "tt", "--oo-- ")) -- shows some initial 'indent'



t.m = {}

t.g.a = {}

t.g.a.c = t

t.tt = tt.new

t.show = table.show



print("-----------------------------------")

print(table.show(t, "t"))            -- most typical use



print("-----------------------------------")

print(table.show(math.tan, "tan"))   -- not a table is OK



print("-----------------------------------")

s = "a string"

print(table.show(s, "s"))            -- not a table is OK

The output:


-----------------------------------

__unnamed__ = {

   [1] = 1;

   [2] = {

      [1] = 2;

      [2] = 3;

      [3] = 4;

   };

   ["default"] = {

      [1] = "a";

      [2] = "b";

      ["e"] = 14;

      ["d"] = {

         [1] = 12;

         [2] = "w";

      };

   };

   ["g"] = {}; -- __unnamed__["default"] (self reference)

};

__unnamed__["g"] = __unnamed__["default"];



-----------------------------------

--oo-- tt = {

--oo--    [1] = 1;

--oo--    [2] = 2;

--oo--    ["function: 0x8070e20, defined in (28-99)@newprint_r.lua"] = "OK";

--oo--    ["h"] = {

--oo--       ["b"] = "e";

--oo--       ["c"] = {

--oo--          ["color"] = 3;

--oo--          ["name"] = "abc";

--oo--       };

--oo--       ["p-q"] = "a";

--oo--    };

--oo-- };



-----------------------------------

t = {

   [1] = 1;

   [2] = {

      [1] = 2;

      [2] = 3;

      [3] = 4;

   };

   ["m"] = {};

   ["show"] = "function: 0x8070e20, defined in (28-99)@newprint_r.lua";

   ["g"] = {

      [1] = "a";

      [2] = "b";

      ["e"] = 14;

      ["d"] = {

         [1] = 12;

         [2] = "w";

      };

      ["a"] = {

         ["c"] = {}; -- t (self reference)

      };

   };

   ["default"] = {}; -- t["g"] (self reference)

};

t["g"]["a"]["c"] = t;

t["default"] = t["g"];



-----------------------------------

tan = "function: 0x806f758, C function"

-----------------------------------

s = "a string"

(the above code originally existed in MakingLuaLikePhp)

Warning: the above does not work properly as shown here:


x = {1, 2, 3}

x[x]=x

print(table.show(x))



--[[output:

__unnamed__ = {

   [1] = 1;

   [2] = 2;

   [3] = 3;

   ["table: 0x695f08"] = {}; -- __unnamed__ (self reference)

};

__unnamed__["table: 0x695f08"] = __unnamed__;

--]]


RecentChanges · preferences
edit · history
Last edited July 14, 2014 11:54 pm GMT (diff)