lolly/gjson/api.go
xfy ce80352e79 feat(gjson): add encode_sort_keys option for stable JSON output
Add configurable key sorting for JSON object encoding. When enabled,
object keys are sorted alphabetically for deterministic output.
Default is disabled for better performance.

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

194 lines
5.0 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
}
// cfgEncodeSortKeys configures whether to sort object keys for stable output.
// Lua: gjson.encode_sort_keys([sort])
// Returns current value when called without arguments.
// When enabled, object keys are sorted alphabetically for deterministic output.
// When disabled (default), keys are output in arbitrary order for better performance.
func (g *GJSON) cfgEncodeSortKeys(L *glua.LState) int {
if L.GetTop() == 0 {
L.Push(glua.LBool(g.config.encodeSortKeys))
return 1
}
g.config.encodeSortKeys = L.CheckBool(1)
L.Push(glua.LBool(g.config.encodeSortKeys))
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)
}