Log In  


As part of a project I'm working on, I wrote a quick json parser that loads a json file into a picotron table and thought it might be worth sharing.

E.g.

{
	"a":{"b":1,"c":-3.5},
	"d":["e",true,false]
}

is converted to:

{
	a={
		b=1,
		c=-3.5
	},
	d={"e",true,false}
}

So if you have some data in json format already, it's easy enough to load in. Hopefully a bit useful for storing configurations or data needed by carts outside of the code itself for neatness.

It should follow json specifications (e.g. whitespace outside of strings doesn't matter), and any null values will be handed as picotron's nil - however, because of the ways nils are added to tables, they won't work/appear in the way it does in the json itself.

Also, if the parser runs into any issues, it tries to offer some helpful errors to identify where the problem is.

I've done some light testing of the code, but let me know if you run into any issues with it.

Code is hidden below:

function init_json_reader()
	J_WHITESPACE = {}
	J_WHITESPACE[" "]=true
	J_WHITESPACE["	"]=true
	J_WHITESPACE[chr(10)]=true
	J_WHITESPACE[chr(13)]=true

	J_START_TABLE = "{"
	J_STOP_TABLE="}"
	J_START_LIST="["
	J_STOP_LIST="]"
	J_QUOTE="\""
	J_COLON=":"
	J_COMMA=","
	J_OBJ_STARTS={
		n=read_json_null,
		t=read_json_true,
		f=read_json_false,
	}
	J_OBJ_STARTS[J_QUOTE]=read_json_key_string
	J_OBJ_STARTS[J_START_TABLE]=read_json_table
	J_OBJ_STARTS[J_START_LIST]=read_json_list
	J_OBJ_STARTS["-"]=read_json_number

	for i = 0,9 do
		J_OBJ_STARTS[tostr(i)] = read_json_number
	end
	json_init = true
end

function load_json_file(filepath)
	-- Load and read a json file and return a list or table
	if not json_init then init_json_reader() end
	local text = fetch(filepath)
	assert(text!=nil,"Failed to load json file: "..filepath)
	return read_json(text)
end

function read_json(string)
	if not json_init then init_json_reader() end
	-- Read a json string and return a list or table.
	if #string == 0 then
		return nil
	end

	local i=skip_json_whitespace(string,1)

	if string[i] == J_START_TABLE then
		return read_json_table(string,i)
	elseif string[i] == J_START_LIST then
		return read_json_list(string,i)
	else
		assert(false,"Unexpected initial character encountered in json file: "..string[i])
	end
end

function skip_json_whitespace(string,i)
	-- Skip to the first non-whitespace character from position i
	while J_WHITESPACE[string[i]] do
		i+=1
		assert(i<=#string,"Unexpectedly hit end of file while skipping whitespace\nin json file")
	end
	return i
end

function read_json_table(string,i)
	local eot = false
	local tab = {}
	local k, v = nil, nil

	if string[i]==J_START_TABLE then
		i+=1
	end

	while not eot do
		k, v, i = read_json_table_entry(string, i)
		tab[k] = v
		i = skip_json_whitespace(string,i)
		if string[i]==J_COMMA then
			i+=1
		elseif string[i]==J_STOP_TABLE then
			i+=1
			eot=true
		else
			assert(
				false,
				"Unexpected character encounted after reading json entry with\nkey '"..tostr(k).."': "..tostr(string[i]).." "
			)
		end
	end
	return tab, i
end

function read_json_table_entry(string, i)
	local k, v = nil, nil
	i = skip_json_whitespace(string,i)
	k, i = read_json_key_string(string,i)
	i = skip_json_whitespace(string,i)
	assert(
		string[i] == J_COLON,
		"Expected colon following json key '"..k.."', found: "..string[i]
	)
	i = skip_json_whitespace(string,i+1)
	assert(
		J_OBJ_STARTS[string[i]]!=nil,
		"Unexpected value encounted while reading json entry\n'"..k.."', found: "..string[i]
	)
	v,i=J_OBJ_STARTS[string[i]](string,i)
	return k, v, i
end

function read_json_key_string(string,i)
	assert(
		string[i]!=J_STOP_TABLE,
		"Table ended while expecting entry, make sure you don't have a misplaced comma."
	)
	assert(
		string[i]==J_QUOTE,
		"Expected json key/string to start with double quote,\ninstead found: "..sub(string,i,i+10).."..."
	)
	i+=1

	local s = i	

	while string[i]!=J_QUOTE do
		i+=1
		assert(
			i<=#string,
			"Encountered end of json while reading key/string:\n"..sub(string,i,i+10).."..."
		)
	end
	return sub(string,s,i-1), i+1
end

function read_json_list(string, i)
	local eol = false
	local lis = {}
	local value = nil

	if string[i]==J_START_LIST then
		i+=1
	end

	while not eol do
		i = skip_json_whitespace(string,i)
		assert(
			string[i]!=J_STOP_LIST,
			"List ended while expecting entry, make sure you don't have a misplaced comma."
		)
		assert(
			J_OBJ_STARTS[string[i]]!=nil,
			"Unexpected value encounted while reading json list,\nfound: "..sub(string,i,i+10).."..."
		)
		value,i=J_OBJ_STARTS[string[i]](string,i)	

		add(lis,value)

		i = skip_json_whitespace(string,i)
		if string[i]==J_COMMA then
			i+=1
		elseif string[i]==J_STOP_LIST then
			i+=1
			eol=true
		else
			assert(
				false,
				"Unexpected character encounted after reading json list entry: "..string[i]
			)
		end
	end
	return lis, i
end

function read_json_null(string,i)
	assert(sub(string,i,i+3)=="null","Was expecting to read null during json file read, instead\nfound: "..sub(string,i,i+10).."...")
	i+=4
	return nil, i
end

function read_json_true(string,i)
	assert(sub(string,i,i+3)=="true","Was expecting to read true during json file read, instead\nfound: "..sub(string,i,i+10).."...")
	i+=4
	return true, i
end

function read_json_false(string,i)
	assert(sub(string,i,i+4)=="false","Was expecting to read false during json file read, instead\nfound: "..sub(string,i,i+10).."...")
	i+=5
	return false, i
end

function read_json_number(string,i)
	local s = i

	while not (
		J_WHITESPACE[string[i]] or 
		string[i]==J_COMMA or 
		string[i]==J_STOP_TABLE or
		string[i]==J_STOP_LIST
	) do
		i+=1
		assert(i<=#string,"Unexpectedly hit the end of json string while reading a number.")
	end

	return tonum(sub(string,s,i-1)), i
end

3


2

I'm currently working on a app to control the Spotify app from Picotron and this helps immensely with connecting with the server, thanks!


1

Awesome, glad to hear it. Also, the app sounds cool!



[Please log in to post a comment]