package json import ( "encoding/json" "errors" "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 } var api = map[string]lua.LGFunction{ "decode": apiDecode, "encode": apiEncode, } func apiDecode(L *lua.LState) int { str := L.CheckString(1) value, err := Decode(L, []byte(str)) if err != nil { L.Push(lua.LNil) L.Push(lua.LString(err.Error())) return 2 } L.Push(value) return 1 } func apiEncode(L *lua.LState) int { value := L.CheckAny(1) data, err := Encode(value) if err != nil { L.Push(lua.LNil) L.Push(lua.LString(err.Error())) return 2 } L.Push(lua.LString(string(data))) return 1 } var ( errNested = errors.New("cannot encode recursively nested tables to JSON") errSparseArray = errors.New("cannot encode sparse array") errInvalidKeys = errors.New("cannot encode mixed or invalid key types") ) type invalidTypeError lua.LValueType func (i invalidTypeError) Error() string { return `cannot encode ` + lua.LValueType(i).String() + ` to JSON` } // Encode returns the JSON encoding of value. func Encode(value lua.LValue) ([]byte, error) { return json.Marshal(jsonValue{ LValue: value, visited: make(map[*lua.LTable]bool), }) } type jsonValue struct { lua.LValue visited map[*lua.LTable]bool } func (j jsonValue) MarshalJSON() (data []byte, err error) { switch converted := j.LValue.(type) { case lua.LBool: data, err = json.Marshal(bool(converted)) case lua.LNumber: data, err = json.Marshal(float64(converted)) case *lua.LNilType: data = []byte(`null`) case lua.LString: data, err = json.Marshal(string(converted)) case *lua.LTable: if j.visited[converted] { return nil, errNested } j.visited[converted] = true key, value := converted.Next(lua.LNil) switch key.Type() { case lua.LTNil: // empty table data = []byte(`[]`) case lua.LTNumber: arr := make([]jsonValue, 0, converted.Len()) expectedKey := lua.LNumber(1) for key != lua.LNil { if key.Type() != lua.LTNumber { err = errInvalidKeys return } if expectedKey != key { err = errSparseArray return } arr = append(arr, jsonValue{value, j.visited}) expectedKey++ key, value = converted.Next(key) } data, err = json.Marshal(arr) case lua.LTString: obj := make(map[string]jsonValue) for key != lua.LNil { if key.Type() != lua.LTString { err = errInvalidKeys return } obj[key.String()] = jsonValue{value, j.visited} key, value = converted.Next(key) } data, err = json.Marshal(obj) default: err = errInvalidKeys } default: err = invalidTypeError(j.LValue.Type()) } return } // Decode converts the JSON encoded data to Lua values. func Decode(L *lua.LState, data []byte) (lua.LValue, error) { var value interface{} err := json.Unmarshal(data, &value) if err != nil { return nil, err } return DecodeValue(L, value), nil } // DecodeValue converts the value to a Lua value. // // This function only converts values that the encoding/json package decodes to. // All other values will return lua.LNil. func DecodeValue(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 json.Number: return lua.LString(converted) case []interface{}: arr := L.CreateTable(len(converted), 0) for _, item := range converted { arr.Append(DecodeValue(L, item)) } return arr case map[string]interface{}: tbl := L.CreateTable(0, len(converted)) for key, item := range converted { tbl.RawSetH(lua.LString(key), DecodeValue(L, item)) } return tbl case nil: return lua.LNil } return lua.LNil }