Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Passing Lua tables to JS functions using JSON #70

Closed
jlimas opened this issue Dec 20, 2021 · 7 comments
Closed

Passing Lua tables to JS functions using JSON #70

jlimas opened this issue Dec 20, 2021 · 7 comments

Comments

@jlimas
Copy link

jlimas commented Dec 20, 2021

I have an use case where I want to be able to pass lua tables to javascript functions using fengari-interop. Soon enough I noticed that they are passed as weakmaps making impossible to work with them in javascript without knowing in advance the keys of the object passed.

I found a workaround this by converting the lua table to a JSON string using (https://github.com/rxi/json.lua) in the Lua code before calling the javascript function inside Lua and then converting the JSON string to JS object in javascript side.
This works fine but I need to create "proxy" methods to allow the users to use this functions transparently.

Ex.

function Context.set (key, value)
    if type(value) == "table" then value = JSON.encode(value) end
    Context._set(key, value)
end

The users can pass a Lua table to the 'public' API is context.set which then internally calls another context._set with the JSON string as argument. The issue here is that everytime we have a new agument or a new function we need to add this 'proxy' functions.

Is there a way to modify the fengari-interop default conversion for LUA_TTABLE arguments to become regular strings (JSON)?

const tojs = function(L, idx) {
	switch(lua_type(L, idx)) {
		case LUA_TNONE:
		case LUA_TNIL:
			return void 0;
		case LUA_TBOOLEAN:
			return lua_toboolean(L, idx);
		case LUA_TLIGHTUSERDATA:
			return lua_touserdata(L, idx);
		case LUA_TNUMBER:
			return lua_tonumber(L, idx);
		case LUA_TSTRING:
			return lua_tojsstring(L, idx);
		case LUA_TUSERDATA: {
			let u = testjs(L, idx);
			if (u !== void 0)
				return u;
		}
		/* fall through */
		case LUA_TTABLE:                      <------- Convert the Lua Table to JSON string
                        **something here**            <------ in Javascript and pass the value as if it
                        return lua_tojsstring(L, idx) <------ was a regular string?
		case LUA_TFUNCTION:
		case LUA_TTHREAD:
		/* fall through */
		default:
			return wrap(L, lua_toproxy(L, idx));
}

I appreciate any pointer for this issue, and thanks again for this library :)

@daurnimator
Copy link
Member

It seems like you're looking for createproxy

@jlimas
Copy link
Author

jlimas commented Dec 20, 2021

Thanks for the reply, I'm stilll a bit lost :(

Would I use the createproxy in Lua? Because I'm getting this error
Cannot convert object to primitive value in line 0

The part I'm really lost is how to retrieve the values of the table in the L state by idx.

@daurnimator
Copy link
Member

Would I use the createproxy in Lua?

Yes. you use it to make lua tables look and act like javascript objects; partially controlled via the lua metatable.

@jlimas
Copy link
Author

jlimas commented Mar 2, 2022

Just in case anyone ends up here with a similar issue, I was able to resolve it by using a Lua JSON library and the following code.

I appended this code before the execution of the Lua code that will be passing a Lua table to JavaScript.

JSON = require "json"

js.global.tojson = function(this, value)
  if type(value) == 'table' then return JSON.encode(value) end
  return value
end

Then when I was getting the arguments from the stack in JavaScript land I used that function as follows.

const top = lua.lua_gettop(L);

const args = [];
for (let x = 1; x <= top; x++) {
  const jsvalue = global.tojson(interop.tojs(L, x));
  args.push(jsvalue);
}

From there the JavaScript function will receive the Lua table argument as a JSON string which can be converted to object with JSON.parse

@jlimas jlimas closed this as completed Mar 2, 2022
@lorenzos
Copy link

lorenzos commented Mar 2, 2022

I use this recursive function, which basically extends interop.tojs to read arrays (i.e. tables with something defined at index 1) and objects (tables without something defined at index 1) from stack position idx:

read = (L, idx) => {
	const type = fengari.lua.lua_type(L, idx);
	if (type == fengari.lua.LUA_TTABLE) {
		const type1 = fengari.lua.lua_geti(L, idx, 1); // Check value type at index 1
		fengari.lua.lua_pop(L, 1);
		if (type1 != fengari.lua.LUA_TNIL) { // Value at index 1 is not NIL, I read this as array
			const array = [];
			for (let i = 1; ; i++) {
				const typei = fengari.lua.lua_geti(L, idx, i);
				const value = read(L, -1);
				fengari.lua.lua_pop(L, 1);
				if (typei == fengari.lua.LUA_TNIL) break; // Stop searching when an element is NIL
				array.push(value);
			}
			return array;
		} else {
			const object = {};
			fengari.lua.lua_pushnil(L); // https://www.lua.org/manual/5.3/manual.html#lua_next
			const t = idx < 0 ? idx - 1 : idx; // New table index, because of the key at the top of the stack
			while (fengari.lua.lua_next(L, t) != 0) {
				const key = fengari.lua.lua_tojsstring(L, -2);
				const value = read(L, -1);
				fengari.lua.lua_pop(L, 1);
				object[key] = value;
			}
			return object;
		}
	} else if (type == fengari.lua.LUA_TNIL) {
		return null;
	} else {
		return fengari.interop.tojs(L, idx);
	}
}

@jlimas
Copy link
Author

jlimas commented Mar 2, 2022

Yeah, we were doing something similar but we were having a lot of problems with nested complex objects like the kind you'll use to send HTTP payloads. We needed a way to work them in Lua as tables to create/update keys and then send them to JavaScript and still be able to work with those objects. That's why serializing as JSON seemed like the best option but since we define a lot of functions we were missing this piece to automatically transform the arguments without doing anything special on Lua code.

@daurnimator
Copy link
Member

We needed a way to work them in Lua as tables to create/update keys and then send them to JavaScript and still be able to work with those objects

The way I've usually done this is using javascript objects from the start. https://gist.github.com/daurnimator/5a7fa933e96e14333962093322e0ff95/
Related: #44

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants