mirror of https://github.com/tidwall/tile38.git
Merge pull request #235 from rshura/lua_json
Add json library (encode/decode methods) to lua.
This commit is contained in:
commit
5945ae2a68
|
@ -222,9 +222,15 @@
|
||||||
revision = "f92cdcd7dcdc69e81b2d7b338479a19a8723cfa3"
|
revision = "f92cdcd7dcdc69e81b2d7b338479a19a8723cfa3"
|
||||||
version = "v1.6.0"
|
version = "v1.6.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "layeh.com/gopher-json"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "c128cc74278be889c4381681712931976fe0d88b"
|
||||||
|
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "4766bb1bebb736256ed12e864e0810ebf21e26e9fee09fa1884cda8072982155"
|
inputs-digest = "d03f1946a6c426327998ec09c8ed935b234a8ca47e2bea55dd6f9d77424f0d60"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/tidwall/resp"
|
"github.com/tidwall/resp"
|
||||||
"github.com/tidwall/tile38/controller/server"
|
"github.com/tidwall/tile38/controller/server"
|
||||||
"github.com/yuin/gopher-lua"
|
"github.com/yuin/gopher-lua"
|
||||||
|
luajson "layeh.com/gopher-json"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -150,6 +151,9 @@ func (pl *lStatePool) New() *lua.LState {
|
||||||
}
|
}
|
||||||
L.SetGlobal("tile38", L.SetFuncs(L.NewTable(), exports))
|
L.SetGlobal("tile38", L.SetFuncs(L.NewTable(), exports))
|
||||||
|
|
||||||
|
// Load json
|
||||||
|
L.SetGlobal("json", L.Get(luajson.Loader(L)))
|
||||||
|
|
||||||
// Prohibit creating new globals in this state
|
// Prohibit creating new globals in this state
|
||||||
lockNewGlobals := func(ls *lua.LState) int {
|
lockNewGlobals := func(ls *lua.LState) int {
|
||||||
ls.RaiseError("attempt to create global variable '%s'", ls.ToString(2))
|
ls.RaiseError("attempt to create global variable '%s'", ls.ToString(2))
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
|
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||||
|
distribute this software, either in source code form or as a compiled
|
||||||
|
binary, for any purpose, commercial or non-commercial, and by any
|
||||||
|
means.
|
||||||
|
|
||||||
|
In jurisdictions that recognize copyright laws, the author or authors
|
||||||
|
of this software dedicate any and all copyright interest in the
|
||||||
|
software to the public domain. We make this dedication for the benefit
|
||||||
|
of the public at large and to the detriment of our heirs and
|
||||||
|
successors. We intend this dedication to be an overt act of
|
||||||
|
relinquishment in perpetuity of all present and future rights to this
|
||||||
|
software under copyright law.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,7 @@
|
||||||
|
# gopher-json [![GoDoc](https://godoc.org/layeh.com/gopher-json?status.svg)](https://godoc.org/layeh.com/gopher-json)
|
||||||
|
|
||||||
|
Package json is a simple JSON encoder/decoder for [gopher-lua](https://github.com/yuin/gopher-lua).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Public domain.
|
|
@ -0,0 +1,40 @@
|
||||||
|
package json // import "layeh.com/gopher-json"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/yuin/gopher-lua"
|
||||||
|
)
|
||||||
|
|
||||||
|
var api = map[string]lua.LGFunction{
|
||||||
|
"decode": apiDecode,
|
||||||
|
"encode": apiEncode,
|
||||||
|
}
|
||||||
|
|
||||||
|
func apiDecode(L *lua.LState) int {
|
||||||
|
str := L.CheckString(1)
|
||||||
|
|
||||||
|
var value interface{}
|
||||||
|
err := json.Unmarshal([]byte(str), &value)
|
||||||
|
if err != nil {
|
||||||
|
L.Push(lua.LNil)
|
||||||
|
L.Push(lua.LString(err.Error()))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
L.Push(fromJSON(L, value))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func apiEncode(L *lua.LState) int {
|
||||||
|
value := L.CheckAny(1)
|
||||||
|
|
||||||
|
visited := make(map[*lua.LTable]bool)
|
||||||
|
data, err := toJSON(value, visited)
|
||||||
|
if err != nil {
|
||||||
|
L.Push(lua.LNil)
|
||||||
|
L.Push(lua.LString(err.Error()))
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
L.Push(lua.LString(string(data)))
|
||||||
|
return 1
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
// Package json is a simple JSON encoder/decoder for gopher-lua.
|
||||||
|
//
|
||||||
|
// Documentation
|
||||||
|
//
|
||||||
|
// The following functions are exposed by the library:
|
||||||
|
// decode(string): Decodes a JSON string. Returns nil and an error string if
|
||||||
|
// the string could not be decoded.
|
||||||
|
// encode(value): Encodes a value into a JSON string. Returns nil and an error
|
||||||
|
// string if the value could not be encoded.
|
||||||
|
//
|
||||||
|
// Example
|
||||||
|
//
|
||||||
|
// Below is an example usage of the library:
|
||||||
|
// L := lua.NewState()
|
||||||
|
// luajson.Preload(s)
|
||||||
|
package json // import "layeh.com/gopher-json"
|
|
@ -0,0 +1,21 @@
|
||||||
|
package json // import "layeh.com/gopher-json"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/yuin/gopher-lua"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Preload adds json to the given Lua state's package.preload table. After it
|
||||||
|
// has been preloaded, it can be loaded using require:
|
||||||
|
//
|
||||||
|
// local json = require("json")
|
||||||
|
func Preload(L *lua.LState) {
|
||||||
|
L.PreloadModule("json", Loader)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loader is the module loader function.
|
||||||
|
func Loader(L *lua.LState) int {
|
||||||
|
t := L.NewTable()
|
||||||
|
L.SetFuncs(t, api)
|
||||||
|
L.Push(t)
|
||||||
|
return 1
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
package json // import "layeh.com/gopher-json"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/yuin/gopher-lua"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSimple(t *testing.T) {
|
||||||
|
const str = `
|
||||||
|
local json = require("json")
|
||||||
|
assert(type(json) == "table")
|
||||||
|
assert(type(json.decode) == "function")
|
||||||
|
assert(type(json.encode) == "function")
|
||||||
|
|
||||||
|
assert(json.encode(true) == "true")
|
||||||
|
assert(json.encode(1) == "1")
|
||||||
|
assert(json.encode(-10) == "-10")
|
||||||
|
assert(json.encode(nil) == "{}")
|
||||||
|
|
||||||
|
local obj = {"a",1,"b",2,"c",3}
|
||||||
|
local jsonStr = json.encode(obj)
|
||||||
|
local jsonObj = json.decode(jsonStr)
|
||||||
|
for i = 1, #obj do
|
||||||
|
assert(obj[i] == jsonObj[i])
|
||||||
|
end
|
||||||
|
|
||||||
|
local obj = {name="Tim",number=12345}
|
||||||
|
local jsonStr = json.encode(obj)
|
||||||
|
local jsonObj = json.decode(jsonStr)
|
||||||
|
assert(obj.name == jsonObj.name)
|
||||||
|
assert(obj.number == jsonObj.number)
|
||||||
|
|
||||||
|
local obj = {"a","b",what="c",[5]="asd"}
|
||||||
|
local jsonStr = json.encode(obj)
|
||||||
|
local jsonObj = json.decode(jsonStr)
|
||||||
|
assert(obj[1] == jsonObj["1"])
|
||||||
|
assert(obj[2] == jsonObj["2"])
|
||||||
|
assert(obj.what == jsonObj["what"])
|
||||||
|
assert(obj[5] == jsonObj["5"])
|
||||||
|
|
||||||
|
assert(json.decode("null") == nil)
|
||||||
|
|
||||||
|
assert(json.decode(json.encode({person={name = "tim",}})).person.name == "tim")
|
||||||
|
|
||||||
|
local obj = {
|
||||||
|
abc = 123,
|
||||||
|
def = nil,
|
||||||
|
}
|
||||||
|
local obj2 = {
|
||||||
|
obj = obj,
|
||||||
|
}
|
||||||
|
obj.obj2 = obj2
|
||||||
|
assert(json.encode(obj) == nil)
|
||||||
|
`
|
||||||
|
s := lua.NewState()
|
||||||
|
Preload(s)
|
||||||
|
if err := s.DoString(str); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomRequire(t *testing.T) {
|
||||||
|
const str = `
|
||||||
|
local j = require("JSON")
|
||||||
|
assert(type(j) == "table")
|
||||||
|
assert(type(j.decode) == "function")
|
||||||
|
assert(type(j.encode) == "function")
|
||||||
|
`
|
||||||
|
s := lua.NewState()
|
||||||
|
s.PreloadModule("JSON", Loader)
|
||||||
|
if err := s.DoString(str); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
package json // import "layeh.com/gopher-json"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/yuin/gopher-lua"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errFunction = errors.New("cannot encode function to JSON")
|
||||||
|
errChannel = errors.New("cannot encode channel to JSON")
|
||||||
|
errState = errors.New("cannot encode state to JSON")
|
||||||
|
errUserData = errors.New("cannot encode userdata to JSON")
|
||||||
|
errNested = errors.New("cannot encode recursively nested tables to JSON")
|
||||||
|
)
|
||||||
|
|
||||||
|
type jsonValue struct {
|
||||||
|
lua.LValue
|
||||||
|
visited map[*lua.LTable]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j jsonValue) MarshalJSON() ([]byte, error) {
|
||||||
|
return toJSON(j.LValue, j.visited)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toJSON(value lua.LValue, visited map[*lua.LTable]bool) (data []byte, err error) {
|
||||||
|
switch converted := value.(type) {
|
||||||
|
case lua.LBool:
|
||||||
|
data, err = json.Marshal(converted)
|
||||||
|
case lua.LChannel:
|
||||||
|
err = errChannel
|
||||||
|
case lua.LNumber:
|
||||||
|
data, err = json.Marshal(converted)
|
||||||
|
case *lua.LFunction:
|
||||||
|
err = errFunction
|
||||||
|
case *lua.LNilType:
|
||||||
|
data, err = json.Marshal(converted)
|
||||||
|
case *lua.LState:
|
||||||
|
err = errState
|
||||||
|
case lua.LString:
|
||||||
|
data, err = json.Marshal(converted)
|
||||||
|
case *lua.LTable:
|
||||||
|
var arr []jsonValue
|
||||||
|
var obj map[string]jsonValue
|
||||||
|
|
||||||
|
if visited[converted] {
|
||||||
|
panic(errNested)
|
||||||
|
}
|
||||||
|
visited[converted] = true
|
||||||
|
|
||||||
|
converted.ForEach(func(k lua.LValue, v lua.LValue) {
|
||||||
|
i, numberKey := k.(lua.LNumber)
|
||||||
|
if numberKey && obj == nil {
|
||||||
|
index := int(i) - 1
|
||||||
|
if index != len(arr) {
|
||||||
|
// map out of order; convert to map
|
||||||
|
obj = make(map[string]jsonValue)
|
||||||
|
for i, value := range arr {
|
||||||
|
obj[strconv.Itoa(i+1)] = value
|
||||||
|
}
|
||||||
|
obj[strconv.Itoa(index+1)] = jsonValue{v, visited}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
arr = append(arr, jsonValue{v, visited})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if obj == nil {
|
||||||
|
obj = make(map[string]jsonValue)
|
||||||
|
for i, value := range arr {
|
||||||
|
obj[strconv.Itoa(i+1)] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
obj[k.String()] = jsonValue{v, visited}
|
||||||
|
})
|
||||||
|
if obj != nil {
|
||||||
|
data, err = json.Marshal(obj)
|
||||||
|
} else {
|
||||||
|
data, err = json.Marshal(arr)
|
||||||
|
}
|
||||||
|
case *lua.LUserData:
|
||||||
|
// TODO: call metatable __tostring?
|
||||||
|
err = errUserData
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromJSON(L *lua.LState, value interface{}) lua.LValue {
|
||||||
|
switch converted := value.(type) {
|
||||||
|
case bool:
|
||||||
|
return lua.LBool(converted)
|
||||||
|
case float64:
|
||||||
|
return lua.LNumber(converted)
|
||||||
|
case string:
|
||||||
|
return lua.LString(converted)
|
||||||
|
case []interface{}:
|
||||||
|
arr := L.CreateTable(len(converted), 0)
|
||||||
|
for _, item := range converted {
|
||||||
|
arr.Append(fromJSON(L, item))
|
||||||
|
}
|
||||||
|
return arr
|
||||||
|
case map[string]interface{}:
|
||||||
|
tbl := L.CreateTable(0, len(converted))
|
||||||
|
for key, item := range converted {
|
||||||
|
tbl.RawSetH(lua.LString(key), fromJSON(L, item))
|
||||||
|
}
|
||||||
|
return tbl
|
||||||
|
}
|
||||||
|
return lua.LNil
|
||||||
|
}
|
Loading…
Reference in New Issue