This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
immibis's profile picture

[Api] Proper Serialization

Started by immibis, 27 February 2012 - 11:47 AM
immibis #1
Posted 27 February 2012 - 12:47 PM
This is like textutils.serialize, but it handles self-referencing tables, handles all datatypes except coroutines, userdata (I don't think CC uses userdata anywhere though) and functions.

You may use this under the terms of the WTFPL.

Usage:

-- Set up a complicated table
local a = {b={c={d={test=1}}}}
a.b.c.d.e = a
a[a.b.c] = {[a]=a,test=2}

print(a.b.c.d.test) -- prints 1
print(a[a.b.c][a][a.b.c].test) -- prints 2

-- Serialize it
local s = serialize(a)
print(s) -- prints gibberish

-- s is a string here - you can send it over modems or whatever (might not work with bundled cable rednet as I'm not sure if that preserves bit 7)

-- Deserialize it
local a2 = deserialize(s)
print(a2.b.c.d.test) -- should print 1
print(a2[a2.b.c][a2][a2.b.c].test) -- should print 2

Code:

local function serializeInt(i)
	local s = ""
	repeat
		s = s .. string.char((i % 128) + ((i >= 128) and 128 or 0))
		i = math.floor(i / 128)
	until i == 0
	return s
end
-- returns int, next position
local function deserializeInt(s,pos)
	local k = pos
	local i = 0
	local m = 1
	while true do
		local b = string.byte(s:sub(k,k))
		i = i + m * (b % 128)
		m = m * 128
		k = k + 1
		if b < 128 then break end
	end
	return i, k
end

local nextid_key = {}
local function serializeInternal(obj, seen)
	if obj ~= nil and seen[obj] then
		return "06" .. serializeInt(seen[obj])
	end
	if type(obj) == "table" then
		local id = seen[nextid_key]
		seen[nextid_key] = id + 1
		seen[obj] = id

		local s = "05"
		local ikeys = {}
		for k,v in ipairs(obj) do
			ikeys[k] = v
			s = s .. serializeInternal(v, seen)
		end
		s = s .. serializeInternal(nil, seen)
		for k,v in pairs(obj) do
			if ikeys[k] == nil then
				s = s .. serializeInternal(k, seen) .. serializeInternal(v, seen)
			end
		end
		s = s .. serializeInternal(nil, seen)
		return s
	elseif type(obj) == "number" then
		local ns = tostring(obj)
		return "04" .. serializeInt(ns:len()) .. ns
	elseif type(obj) == "string" then
		return "03" .. serializeInt(obj:len()) .. obj
	elseif type(obj) == "boolean" then
		if obj then
			return "01"
		else
			return "02"
		end
	elseif type(obj) == "nil" then
		return "00"
	elseif type(obj) == "userdata" then
		error("cannot serialize userdata")
	elseif type(obj) == "thread" then
		error("cannot serialize threads")
	elseif type(obj) == "function" then
		error("cannot serialize functions")
	else
		error("unknown type: " .. type(obj))
	end
end
function serialize(obj)
	return serializeInternal(obj, {[nextid_key] = 0})
end
function deserialize(s)
	local pos = 1
	local seen = {}
	local nextid = 0
	local function internal()
		local tch = s:sub(pos,pos)
		local len
		pos = pos + 1
		if tch == "00" then
			return nil
		elseif tch == "01" then
			return true
		elseif tch == "02" then
			return false
		elseif tch == "03" then
			len, pos = deserializeInt(s, pos)
			local rv = s:sub(pos, pos+len-1)
			pos = pos + len
			return rv
		elseif tch == "04" then
			len, pos = deserializeInt(s, pos)
			local rv = s:sub(pos, pos+len-1)
			pos = pos + len
			return tonumber(rv)
		elseif tch == "05" then
			local id = nextid
			nextid = id + 1
			local t = {}
			seen[id] = t

			local k = 1
			while true do
				local v = internal()
				if v == nil then break end
				t[k] = v
				k = k + 1
			end

			while true do
				local k = internal()
				if k == nil then break end
				local v = internal()
				if v == nil then break end
				t[k] = v
			end
			return t
		elseif tch == "06" then
			local id
			id, pos = deserializeInt(s, pos)
			return seen[id]
		else
			return nil
		end
	end
	return internal()
end

edit: fixed typo in example (s instead of a2)
vvenaya #2
Posted 27 February 2012 - 03:33 PM
Excelent :D/>/>
M1cr0man #3
Posted 18 March 2012 - 11:54 PM
Great code! It even fixed the problem with control characters in strings in tables. :D/>/>
Casper7526 #4
Posted 19 March 2012 - 03:02 AM
Should be default in CC
dmillerw #5
Posted 25 March 2012 - 09:59 PM
Having a bit of a problem. Whenever I attempt to use the serialize function, I always get an error.

lua:43: attempt to call table
immibis #6
Posted 12 April 2012 - 12:49 PM
Fixed a pretty serious typo, which made it not save the boolean value false properly.
immibis #7
Posted 16 April 2012 - 05:28 AM
dmillerw, whch line is line 43 and what's your code?
Niphred #8
Posted 11 July 2012 - 10:31 AM
You can also serialize (some) functions with string.dump and restore them with loadstring
FuuuAInfiniteLoop(F.A.I.L) #9
Posted 31 March 2013 - 03:53 PM
This should be default in computercraft an it solves a bug that i have seen!

(How fun is make a topic back to life :D/>)
Sxw #10
Posted 31 March 2013 - 05:46 PM
*facepam*
I have never seen someone mention that they are gravedigging a thread.

*slow sarcastic clap*
Cranium #11
Posted 01 April 2013 - 08:10 AM
Preventing further necromancy, by locking this thread. I may have to investigate this upwelling of dark magic into our peaceful forums….