/* Copyright 2022. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package luamanager import ( "encoding/json" "errors" lua "github.com/yuin/gopher-lua" ) const JsonLibName = "json" // OpenJson is the module loader function. func OpenJson(L *lua.LState) int { mod := L.RegisterModule(JsonLibName, jsonFuncs) L.Push(mod) return 1 } var jsonFuncs = map[string]lua.LGFunction{ "decode": jsonDecode, "encode": jsonEncode, } func jsonDecode(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 jsonEncode(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(`null`) 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 }