package json import ( "encoding/json" "errors" lua "github.com/yuin/gopher-lua" ) 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` } 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 } // ValueDecode converts the JSON encoded data to Lua values. func ValueDecode(L *lua.LState, data []byte) (lua.LValue, error) { var value interface{} err := json.Unmarshal(data, &value) if err != nil { return nil, err } return decode(L, value), nil } func decode(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(decode(L, item)) } return arr case map[string]interface{}: tbl := L.CreateTable(0, len(converted)) for key, item := range converted { tbl.RawSetH(lua.LString(key), decode(L, item)) } return tbl case nil: return lua.LNil } panic("unreachable") } // ValueEncode returns the JSON encoding of value. func ValueEncode(value lua.LValue) ([]byte, error) { return json.Marshal(jsonValue{ LValue: value, visited: make(map[*lua.LTable]bool), }) }