summaryrefslogblamecommitdiffstats
path: root/src/Bindings/LuaJson.cpp
blob: 4fa16273c1d4bd4d9a8905f405092f24943a7742 (plain) (tree)


























































































































































































































































































































                                                                                                                  

// LuaJson.cpp

// Implements the Json exposure bindings to Lua

#include "Globals.h"
#include <sstream>
#include "LuaJson.h"
#include "LuaState.h"
#include "tolua++/include/tolua++.h"
#include "json/json.h"





// fwd:

static void PushJsonValue(const Json::Value & a_Value, cLuaState & a_LuaState);
static Json::Value JsonSerializeValue(cLuaState & a_LuaState);





/** Pushes the specified Json array as a table on top of the specified Lua state.
Assumes that a_Value is an array. */
static void PushJsonArray(const Json::Value & a_Value, cLuaState & a_LuaState)
{
	// Create the appropriately-sized Lua table:
	lua_createtable(a_LuaState, static_cast<int>(a_Value.size()), 0);

	// Insert each value to the appropriate index (1-based):
	int idx = 1;
	for (const auto & v: a_Value)
	{
		// Include Json null values in the array - it will have holes, but indices will stay the same
		PushJsonValue(v, a_LuaState);
		lua_rawseti(a_LuaState, -2, idx);
		idx += 1;
	}  // for v: a_Value[]
}





/** Pushes the specified Json object as a table on top of the specified Lua state.
Assumes that a_Value is an object. */
static void PushJsonObject(const Json::Value & a_Value, cLuaState & a_LuaState)
{
	// Create the appropriately-sized Lua table:
	lua_createtable(a_LuaState, 0, static_cast<int>(a_Value.size()));

	// Json::Value has no means of iterating over children with their names included.
	// We need to iterate over names and "find" them in the object again:
	auto names = a_Value.getMemberNames();
	for (const auto & n: names)
	{
		auto v = a_Value[n];
		if (v.isNull())
		{
			// Skip null values
			continue;
		}

		// Set the value in Lua's table:
		a_LuaState.Push(n);
		PushJsonValue(v, a_LuaState);
		lua_rawset(a_LuaState, -3);
	}  // for itr - a_Value[]
}





/** Pushes the specified Json value as an appropriate type on top of the specified Lua state. */
void PushJsonValue(const Json::Value & a_Value, cLuaState & a_LuaState)
{
	switch (a_Value.type())
	{
		case Json::nullValue:
		{
			a_LuaState.PushNil();
			break;
		}

		case Json::intValue:
		case Json::uintValue:
		case Json::realValue:
		{
			a_LuaState.Push(static_cast<lua_Number>(a_Value.asDouble()));
			break;
		}

		case Json::booleanValue:
		{
			a_LuaState.Push(a_Value.asBool());
			break;
		}

		case Json::stringValue:
		{
			a_LuaState.Push(a_Value.asString());
			break;
		}

		case Json::arrayValue:
		{
			PushJsonArray(a_Value, a_LuaState);
			break;
		}

		case Json::objectValue:
		{
			PushJsonObject(a_Value, a_LuaState);
			break;
		}
	}  // switch (v.type())
}





/** Serializes the Lua table at the top of the specified Lua state's stack into a Json value.
Lets jsoncpp decides whether to serialize into an object or an array. */
static Json::Value JsonSerializeTable(cLuaState & a_LuaState)
{
	Json::Value res;
	lua_pushnil(a_LuaState);
	while (lua_next(a_LuaState, -2) != 0)
	{
		if (lua_type(a_LuaState, -2) == LUA_TNUMBER)
		{
			int idx;
			a_LuaState.GetStackValue(-2, idx);
			res[idx - 1] = JsonSerializeValue(a_LuaState);
		}
		else
		{
			AString name;
			if (a_LuaState.GetStackValue(-2, name))
			{
				res[name] = JsonSerializeValue(a_LuaState);
			}
		}
		lua_pop(a_LuaState, 1);
	}
	return res;
}





/** Serializes the Lua value at the top of the specified Lua state into a Json value. */
static Json::Value JsonSerializeValue(cLuaState & a_LuaState)
{
	switch (lua_type(a_LuaState, -1))
	{
		case LUA_TNUMBER:
		{
			lua_Number v;
			a_LuaState.GetStackValue(-1, v);
			return Json::Value(v);
		}
		case LUA_TSTRING:
		{
			AString v;
			a_LuaState.GetStackValue(-1, v);
			return Json::Value(v);
		}
		case LUA_TTABLE:
		{
			return JsonSerializeTable(a_LuaState);
		}
		default:
		{
			LOGD("Attempting to serialize an unhandled Lua value type: %d", lua_type(a_LuaState, -1));
			return Json::Value(Json::nullValue);
		}
	}
}





static int tolua_cJson_Parse(lua_State * a_LuaState)
{
	// Function signature:
	// cJson:Parse("string") -> table

	// Check the param types:
	cLuaState L(a_LuaState);
	if (
		!L.CheckParamUserTable(1, "cJson") ||
		!L.CheckParamString(2) ||
		!L.CheckParamEnd(3)
	)
	{
		return 0;
	}

	// Get the input string:
	AString input;
	if (!L.GetStackValue(2, input))
	{
		LOGWARNING("cJson:Parse(): Cannot read input string");
		L.LogStackTrace();
		return 0;
	}

	// Parse the string:
	Json::Value root;
	Json::Reader reader;
	if (!reader.parse(input, root, false))
	{
		L.PushNil();
		L.Push(Printf("Parsing Json failed: %s", reader.getFormattedErrorMessages().c_str()));
		return 2;
	}

	// Push the Json value onto Lua stack:
	PushJsonValue(root, L);
	return 1;
}





static int tolua_cJson_Serialize(lua_State * a_LuaState)
{
	// Function signature:
	// cJson:Serialize({table}, [{option1 = value1, option2 = value2}]) -> string

	// Check the param types:
	cLuaState L(a_LuaState);
	if (
		!L.CheckParamUserTable(1, "cJson") ||
		!L.CheckParamTable(2) ||
		!L.CheckParamEnd(4)
	)
	{
		return 0;
	}

	// Push the table to the top of the Lua stack, and call the serializing function:
	lua_pushvalue(L, 2);
	Json::Value root = JsonSerializeValue(L);
	lua_pop(L, 1);

	// Create the writer, with all properties (optional param 3) applied to it:
	Json::StreamWriterBuilder builder;
	if (lua_istable(L, 3))
	{
		lua_pushnil(L);
		while (lua_next(L, -2) != 0)
		{
			if (lua_type(L, -2) == LUA_TSTRING)
			{
				AString propName, propValue;
				if (L.GetStackValues(-2, propName, propValue))
				{
					builder[propName] = propValue;
				}
			}
			lua_pop(L, 1);
		}
		// Check for invalid settings:
		Json::Value invalid;
		if (!builder.validate(&invalid))
		{
			LOGINFO("cJson:Serialize(): detected invalid settings:");
			for (const auto & n: invalid.getMemberNames())
			{
				LOGINFO("  \"%s\" (\"%s\")", n.c_str(), invalid[n].asCString());
			}
		}
	}
	auto writer(builder.newStreamWriter());

	// Serialize the string and push it as the return value:
	std::stringstream ss;
	writer->write(root, &ss);
	L.Push(ss.str());
	return 1;
}





void cLuaJson::Bind(cLuaState & a_LuaState)
{
	tolua_beginmodule(a_LuaState, nullptr);

		// Create the cJson API class:
		tolua_usertype(a_LuaState, "cJson");
		tolua_cclass(a_LuaState, "cJson", "cJson", "", nullptr);

		// Fill in the functions (alpha-sorted):
		tolua_beginmodule(a_LuaState, "cJson");
			tolua_function(a_LuaState, "Parse",     tolua_cJson_Parse);
			tolua_function(a_LuaState, "Serialize", tolua_cJson_Serialize);
		tolua_endmodule(a_LuaState);
	tolua_endmodule(a_LuaState);
}