diff --git a/doc.go b/doc.go index ecc915d..8f27121 100644 --- a/doc.go +++ b/doc.go @@ -1,8 +1,9 @@ // Package json is a simple JSON encoder/decoder for gopher-lua. // -// Documentation +// # 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 @@ -15,19 +16,29 @@ // nil | null // number | number // string | string -// table | object: when table is non-empty and has only string keys +// table | object: when table is non-empty and has only string keys or is a sparse array // | array: when table is empty, or has only sequential numeric keys // | starting from 1 // // Attempting to encode any other Lua type will result in an error. // -// Example +// # Example // // Below is an example usage of the library: +// // import ( -// luajson "layeh.com/gopher-json" +// "fmt" +// luajson "github.com/HannesLueer/gopher-json" +// lua "github.com/yuin/gopher-lua" // ) // -// L := lua.NewState() -// luajson.Preload(L) +// func main() { +// L := lua.NewState() +// luaScript := "t = {1, 2, [10] = 3}" +// L.DoString(luaScript) +// luaValue := L.GetGlobal("t") +// t, _ := luajson.Encode(luaValue) +// fmt.Printf("t as json: %s", t) +// } + package json // import "layeh.com/gopher-json" diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a970dc3 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module layeh.com/gopher-json + +go 1.21 + +require github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e69c257 --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 h1:k/gmLsJDWwWqbLCur2yWnJzwQEKRcAHXo6seXGuSwWw= +github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= +golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/json.go b/json.go index c34af2d..f8c84c3 100644 --- a/json.go +++ b/json.go @@ -3,14 +3,13 @@ package json import ( "encoding/json" "errors" - - "github.com/yuin/gopher-lua" + lua "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") +// local json = require("json") func Preload(L *lua.LState) { L.PreloadModule("json", Loader) } @@ -56,7 +55,6 @@ func apiEncode(L *lua.LState) int { 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") ) @@ -102,21 +100,30 @@ func (j jsonValue) MarshalJSON() (data []byte, err error) { data = []byte(`[]`) case lua.LTNumber: arr := make([]jsonValue, 0, converted.Len()) + m := make(map[string]jsonValue) expectedKey := lua.LNumber(1) + isSparseArray := false for key != lua.LNil { if key.Type() != lua.LTNumber { err = errInvalidKeys return } if expectedKey != key { - err = errSparseArray - return + isSparseArray = true + } + if !isSparseArray { + arr = append(arr, jsonValue{value, j.visited}) } - arr = append(arr, jsonValue{value, j.visited}) + m[key.String()] = jsonValue{value, j.visited} + expectedKey++ key, value = converted.Next(key) } - data, err = json.Marshal(arr) + if isSparseArray { + data, err = json.Marshal(m) + } else { + data, err = json.Marshal(arr) + } case lua.LTString: obj := make(map[string]jsonValue) for key != lua.LNil { @@ -131,6 +138,9 @@ func (j jsonValue) MarshalJSON() (data []byte, err error) { default: err = errInvalidKeys } + case *lua.LUserData: + data, err = json.Marshal(nil) + return default: err = invalidTypeError(j.LValue.Type()) } diff --git a/json_test.go b/json_test.go index fbd627d..77f133a 100644 --- a/json_test.go +++ b/json_test.go @@ -2,9 +2,10 @@ package json import ( "encoding/json" + "fmt" "testing" - "github.com/yuin/gopher-lua" + lua "github.com/yuin/gopher-lua" ) func TestSimple(t *testing.T) { @@ -21,9 +22,6 @@ func TestSimple(t *testing.T) { assert(json.encode({}) == "[]") assert(json.encode({1, 2, 3}) == "[1,2,3]") - local _, err = json.encode({1, 2, [10] = 3}) - assert(string.find(err, "sparse array")) - local _, err = json.encode({1, 2, 3, name = "Tim"}) assert(string.find(err, "mixed or invalid key types")) @@ -62,6 +60,16 @@ func TestSimple(t *testing.T) { a[i] = i end assert(json.encode(a) == "[1,2,3,4,5]") + + -- UserData removal + local t = setmetatable({10}, { + __call = function(t, value) + return value + end + }) + + assert(t(37) == 37) + assert(json.encode(t) == "[10]") ` s := lua.NewState() defer s.Close() @@ -97,3 +105,87 @@ func TestDecodeValue_jsonNumber(t *testing.T) { t.Fatalf("expecting LString, got %T", v) } } + +func TestEncode_SparseArray(t *testing.T) { + tests := []struct { + table string + expected string + }{ + { + table: `{ + 1, + 2, + 3, + 4, + 5 + }`, + expected: `[1,2,3,4,5]`, + }, + { + table: `{ + 1, + 2, + [10] = 3 + }`, + expected: `{"1":1,"10":3,"2":2}`, + }, + { + table: `{ + nested = { + [37] = "index 37" + } + }`, + expected: `{"nested":{"37":"index 37"}}`, + }, + { + table: `{ + nested = { + "index 1", + [37] = "index 37" + } + }`, + expected: `{"nested":{"1":"index 1","37":"index 37"}}`, + }, + { + table: `{ + nested = { + [37] = "index 37", + "index 1" + } + }`, + expected: `{"nested":{"1":"index 1","37":"index 37"}}`, + }, + { + table: `{ + nested = { + 1, + 2, + 3, + [2] = 4, + [5] = "index 5" + } + }`, + expected: `{"nested":{"1":1,"2":2,"3":3,"5":"index 5"}}`, + }, + { + table: `{ + [65] = 123, + [67] = 456 + }`, + expected: `{"65":123,"67":456}`, + }, + } + + s := lua.NewState() + defer s.Close() + Preload(s) + for _, test := range tests { + luaScript := fmt.Sprintf(` + local json = require("json") + local t = %s + assert(json.encode(t) == '%s')`, test.table, test.expected) + if err := s.DoString(luaScript); err != nil { + t.Error(err) + } + } +}