lolly/gjson/api.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

177 lines
4.4 KiB
Go

package gjson
import (
"fmt"
glua "github.com/yuin/gopher-lua"
)
// cfgEncodeSparseArray configures sparse array handling.
// Lua: gjson.encode_sparse_array([convert[, ratio[, safe]]])
// Returns current values when called without arguments.
func (g *GJSON) cfgEncodeSparseArray(L *glua.LState) int {
n := L.GetTop()
if n == 0 {
// Return current values
L.Push(glua.LBool(g.config.encodeSparseArray.convert))
L.Push(glua.LNumber(g.config.encodeSparseArray.ratio))
L.Push(glua.LNumber(g.config.encodeSparseArray.safe))
return 3
}
// Set new values
if n >= 1 {
g.config.encodeSparseArray.convert = L.CheckBool(1)
}
if n >= 2 {
g.config.encodeSparseArray.ratio = L.CheckInt(2)
if g.config.encodeSparseArray.ratio < 0 {
L.ArgError(2, "ratio must be >= 0")
}
}
if n >= 3 {
g.config.encodeSparseArray.safe = L.CheckInt(3)
if g.config.encodeSparseArray.safe < 0 {
L.ArgError(3, "safe must be >= 0")
}
}
L.Push(glua.LBool(g.config.encodeSparseArray.convert))
L.Push(glua.LNumber(g.config.encodeSparseArray.ratio))
L.Push(glua.LNumber(g.config.encodeSparseArray.safe))
return 3
}
// cfgEncodeMaxDepth configures the maximum nesting depth for encoding.
// Lua: gjson.encode_max_depth([depth])
func (g *GJSON) cfgEncodeMaxDepth(L *glua.LState) int {
if L.GetTop() == 0 {
L.Push(glua.LNumber(g.config.encodeMaxDepth))
return 1
}
depth := L.CheckInt(1)
if depth < 1 {
L.ArgError(1, "max depth must be >= 1")
}
g.config.encodeMaxDepth = depth
L.Push(glua.LNumber(g.config.encodeMaxDepth))
return 1
}
// cfgDecodeMaxDepth configures the maximum nesting depth for decoding.
// Lua: gjson.decode_max_depth([depth])
func (g *GJSON) cfgDecodeMaxDepth(L *glua.LState) int {
if L.GetTop() == 0 {
L.Push(glua.LNumber(g.config.decodeMaxDepth))
return 1
}
depth := L.CheckInt(1)
if depth < 1 {
L.ArgError(1, "max depth must be >= 1")
}
g.config.decodeMaxDepth = depth
L.Push(glua.LNumber(g.config.decodeMaxDepth))
return 1
}
// cfgEncodeNumberPrecision configures the number precision for encoding.
// Lua: gjson.encode_number_precision([precision])
func (g *GJSON) cfgEncodeNumberPrecision(L *glua.LState) int {
if L.GetTop() == 0 {
L.Push(glua.LNumber(g.config.encodeNumberPrecision))
return 1
}
precision := L.CheckInt(1)
if precision < 1 || precision > 14 {
L.ArgError(1, "precision must be between 1 and 14")
}
g.config.encodeNumberPrecision = precision
L.Push(glua.LNumber(g.config.encodeNumberPrecision))
return 1
}
// cfgEncodeKeepBuffer configures whether to reuse the encoding buffer.
// Lua: gjson.encode_keep_buffer([keep])
func (g *GJSON) cfgEncodeKeepBuffer(L *glua.LState) int {
if L.GetTop() == 0 {
L.Push(glua.LBool(g.config.encodeKeepBuffer))
return 1
}
g.config.encodeKeepBuffer = L.CheckBool(1)
L.Push(glua.LBool(g.config.encodeKeepBuffer))
return 1
}
// encode is the Lua function for gjson.encode(value).
// Returns (json_string, nil) on success or (nil, error_message) on failure.
func (g *GJSON) encode(L *glua.LState) int {
if L.GetTop() != 1 {
L.ArgError(1, "expected 1 argument")
return 0
}
value := L.Get(1)
result, err := g.encodeValue(L, value, 0)
if err != nil {
L.Push(glua.LNil)
L.Push(glua.LString(err.Error()))
return 2
}
L.Push(glua.LString(result))
return 1
}
// decode is the Lua function for gjson.decode(string).
// Returns (value, nil) on success or (nil, error_message) on failure.
func (g *GJSON) decode(L *glua.LState) int {
if L.GetTop() != 1 {
L.ArgError(1, "expected 1 argument")
return 0
}
str := L.CheckString(1)
if str == "" {
L.Push(glua.LNil)
L.Push(glua.LString("empty JSON string"))
return 2
}
result, err := g.decodeValue(L, []byte(str), 0)
if err != nil {
L.Push(glua.LNil)
L.Push(glua.LString(err.Error()))
return 2
}
L.Push(result)
return 1
}
// encodeValue encodes a Lua value to JSON with depth tracking.
func (g *GJSON) encodeValue(L *glua.LState, value glua.LValue, depth int) (string, error) {
if depth > g.config.encodeMaxDepth {
return "", fmt.Errorf("maximum nesting depth %d exceeded", g.config.encodeMaxDepth)
}
return encodeLuaValue(L, value, g.config, depth)
}
// decodeValue decodes JSON to a Lua value with depth tracking.
func (g *GJSON) decodeValue(L *glua.LState, data []byte, depth int) (glua.LValue, error) {
if depth > g.config.decodeMaxDepth {
return glua.LNil, fmt.Errorf("maximum nesting depth %d exceeded", g.config.decodeMaxDepth)
}
return decodeJSONValue(L, data, g.config, g.null, depth)
}