lolly/gjson/gjson_test.go
xfy 376b9fd33c feat(gjson): add Lua JSON encoding/decoding library
Add gjson package providing high-performance JSON encoding/decoding
for gopher-lua with lua-cjson API compatibility. Uses goccy/go-json
as the underlying JSON engine.

Features:
- Full lua-cjson API compatibility for OpenResty migration
- Sparse array detection and handling
- Maximum nesting depth control for encode/decode
- Number precision control
- Independent configuration instances via gjson.new()
- gjson.null sentinel for JSON null values

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 15:44:31 +08:00

352 lines
8.0 KiB
Go

package gjson
import (
"testing"
glua "github.com/yuin/gopher-lua"
)
func TestModuleLoad(t *testing.T) {
L := glua.NewState()
defer L.Close()
Preload(L)
err := L.DoString(`
local gjson = require("gjson")
assert(gjson._NAME == "gjson")
assert(gjson._VERSION ~= nil)
`)
if err != nil {
t.Fatalf("module load failed: %v", err)
}
}
func TestEncodeBasicTypes(t *testing.T) {
L := glua.NewState()
defer L.Close()
Preload(L)
tests := []struct {
name string
script string
expected string
}{
{"null", `return gjson.encode(gjson.null)`, "null"},
{"nil", `return gjson.encode(nil)`, "null"},
{"true", `return gjson.encode(true)`, "true"},
{"false", `return gjson.encode(false)`, "false"},
{"number", `return gjson.encode(42)`, "42"},
{"string", `return gjson.encode("hello")`, `"hello"`},
{"empty object", `return gjson.encode({})`, `{}`},
{"empty array", `return gjson.encode({1})`, `[1]`},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := L.DoString(`
local gjson = require("gjson")
result = ` + tt.script[7:] + `
`)
if err != nil {
t.Fatalf("script failed: %v", err)
}
result := L.GetGlobal("result").String()
if result != tt.expected {
t.Errorf("expected %s, got %s", tt.expected, result)
}
})
}
}
func TestEncodeTable(t *testing.T) {
L := glua.NewState()
defer L.Close()
Preload(L)
err := L.DoString(`
local gjson = require("gjson")
-- Test array
local arr = gjson.encode({1, 2, 3})
assert(arr == "[1,2,3]", "array: " .. arr)
-- Test object
local obj = gjson.encode({name = "Alice", age = 30})
assert(string.find(obj, '"name":"Alice"'), "object name: " .. obj)
assert(string.find(obj, '"age":30'), "object age: " .. obj)
-- Test nested
local nested = gjson.encode({inner = {value = 123}})
assert(string.find(nested, '"inner"'), "nested: " .. nested)
assert(string.find(nested, '"value":123'), "nested value: " .. nested)
`)
if err != nil {
t.Fatalf("test failed: %v", err)
}
}
func TestDecodeBasicTypes(t *testing.T) {
L := glua.NewState()
defer L.Close()
Preload(L)
err := L.DoString(`
local gjson = require("gjson")
-- Test null
local null_val = gjson.decode("null")
assert(null_val == gjson.null, "null decode")
-- Test boolean
local bool_val = gjson.decode("true")
assert(bool_val == true, "true decode")
-- Test number
local num_val = gjson.decode("42")
assert(num_val == 42, "number decode")
-- Test string
local str_val = gjson.decode('"hello"')
assert(str_val == "hello", "string decode")
`)
if err != nil {
t.Fatalf("test failed: %v", err)
}
}
func TestDecodeTable(t *testing.T) {
L := glua.NewState()
defer L.Close()
Preload(L)
err := L.DoString(`
local gjson = require("gjson")
-- Test array
local arr = gjson.decode("[1,2,3]")
assert(arr[1] == 1, "array index 1")
assert(arr[2] == 2, "array index 2")
assert(arr[3] == 3, "array index 3")
-- Test object
local obj = gjson.decode('{"name":"Alice","age":30}')
assert(obj.name == "Alice", "object name")
assert(obj.age == 30, "object age")
`)
if err != nil {
t.Fatalf("test failed: %v", err)
}
}
func TestRoundTrip(t *testing.T) {
L := glua.NewState()
defer L.Close()
Preload(L)
err := L.DoString(`
local gjson = require("gjson")
-- Test round trip
local original = {name = "Bob", values = {1, 2, 3}, active = true}
local encoded = gjson.encode(original)
local decoded = gjson.decode(encoded)
assert(decoded.name == "Bob", "round trip name")
assert(decoded.values[1] == 1, "round trip values[1]")
assert(decoded.values[2] == 2, "round trip values[2]")
assert(decoded.values[3] == 3, "round trip values[3]")
assert(decoded.active == true, "round trip active")
`)
if err != nil {
t.Fatalf("test failed: %v", err)
}
}
func TestErrorHandling(t *testing.T) {
L := glua.NewState()
defer L.Close()
Preload(L)
err := L.DoString(`
local gjson = require("gjson")
-- Test invalid JSON
local result, err = gjson.decode("not valid json")
assert(result == nil, "should return nil on error")
assert(err ~= nil, "should return error message")
-- Test empty string
local result2, err2 = gjson.decode("")
assert(result2 == nil, "should return nil on empty")
assert(err2 ~= nil, "should return error on empty")
`)
if err != nil {
t.Fatalf("test failed: %v", err)
}
}
func TestNewInstance(t *testing.T) {
L := glua.NewState()
defer L.Close()
Preload(L)
err := L.DoString(`
local gjson = require("gjson")
-- Create new instance
local inst = gjson.new()
assert(inst._NAME == "gjson", "instance name")
assert(inst.null ~= nil, "instance null")
-- Test instance encode/decode
local encoded = inst.encode({test = 123})
local decoded = inst.decode(encoded)
assert(decoded.test == 123, "instance round trip")
`)
if err != nil {
t.Fatalf("test failed: %v", err)
}
}
func TestConfigFunctions(t *testing.T) {
L := glua.NewState()
defer L.Close()
Preload(L)
err := L.DoString(`
local gjson = require("gjson")
-- Test encode_max_depth
local depth = gjson.encode_max_depth(500)
assert(depth == 500, "encode_max_depth set")
assert(gjson.encode_max_depth() == 500, "encode_max_depth get")
-- Test decode_max_depth
local ddepth = gjson.decode_max_depth(500)
assert(ddepth == 500, "decode_max_depth set")
-- Test encode_number_precision
local prec = gjson.encode_number_precision(10)
assert(prec == 10, "encode_number_precision set")
-- Test encode_sparse_array
local convert, ratio, safe = gjson.encode_sparse_array(true, 3, 20)
assert(convert == true, "sparse convert")
assert(ratio == 3, "sparse ratio")
assert(safe == 20, "sparse safe")
`)
if err != nil {
t.Fatalf("test failed: %v", err)
}
}
func TestEncodeDepthLimit(t *testing.T) {
L := glua.NewState()
defer L.Close()
Preload(L)
err := L.DoString(`
local gjson = require("gjson")
-- Set very low depth limit
gjson.encode_max_depth(2)
-- Create nested table
local nested = {a = {b = {c = 1}}}
-- Should fail due to depth limit
local result, err = gjson.encode(nested)
assert(result == nil, "should return nil on depth exceeded")
assert(err ~= nil, "should return error on depth exceeded")
assert(string.find(err, "depth"), "error should mention depth")
`)
if err != nil {
t.Fatalf("test failed: %v", err)
}
}
func TestDecodeDepthLimit(t *testing.T) {
L := glua.NewState()
defer L.Close()
Preload(L)
err := L.DoString(`
local gjson = require("gjson")
-- Set very low depth limit
gjson.decode_max_depth(2)
-- Deeply nested JSON
local deep = '{"a":{"b":{"c":1}}}'
-- Should fail due to depth limit
local result, err = gjson.decode(deep)
assert(result == nil, "should return nil on depth exceeded")
assert(err ~= nil, "should return error on depth exceeded")
`)
if err != nil {
t.Fatalf("test failed: %v", err)
}
}
func TestEncodeUnencodableValue(t *testing.T) {
L := glua.NewState()
defer L.Close()
Preload(L)
err := L.DoString(`
local gjson = require("gjson")
-- Try to encode a function (should fail)
local result, err = gjson.encode(function() end)
assert(result == nil, "should return nil for function")
assert(err ~= nil, "should return error for function")
-- Try to encode table with function value (should fail)
local result2, err2 = gjson.encode({func = function() end})
assert(result2 == nil, "should return nil for table with function")
assert(err2 ~= nil, "should return error for table with function")
`)
if err != nil {
t.Fatalf("test failed: %v", err)
}
}
func TestEncodeSparseArray(t *testing.T) {
L := glua.NewState()
defer L.Close()
Preload(L)
err := L.DoString(`
local gjson = require("gjson")
-- Create sparse array (missing index 2)
local sparse = {}
sparse[1] = "a"
sparse[3] = "c"
-- Default: convert sparse to object
local result = gjson.encode(sparse)
-- Should be encoded as object with numeric keys
assert(string.find(result, '"1":"a"'), "should have key 1")
assert(string.find(result, '"3":"c"'), "should have key 3")
`)
if err != nil {
t.Fatalf("test failed: %v", err)
}
}