背景
qjson.encode 目标是成为常见 lua-cjson encode 用法的平滑迁移路径(drop-in),
同时保留 lua-cjson 无法提供的一项优势:
- 原生编码 qjson lazy proxy,包括 mutation 之后的 dirty lazy object(clean proxy
通过原样切取原始 JSON 字节输出,这同时无损保留了大整数)。
本 issue 定义目标行为与需补充的测试覆盖,具体实现另行跟踪。所有对比的参照编码器为
vendored lua-cjson(基于 OpenResty cjson 2.1.0.11 实测核对)。
行为策略
- 普通 Lua 输入:遵循
lua-cjson 语义。
- qjson lazy proxy:保留 qjson 原生编码,即使 lua-cjson 无法正确编码它们。
- 两个编码器都拒绝的不支持值:qjson 只需一致地拒绝;错误文案不要求与 lua-cjson 逐字一致。
数组 vs 对象 判定规则(核心)
显式写明,因为仅凭样例无法确定边界:
- 当且仅当所有键都是正整数(
>= 1)时,table 编码为 JSON 数组。数组跨度为
1..max,缺失的下标输出为 null。即使缺少下标 1 也成立:{[2]="b"} -> [null,"b"]。
- 否则编码为 JSON 对象,且所有键都被字符串化(含数字键):
{[1]="a",[2]="b",x="c"} -> {"1":"a","2":"b","x":"c"}。
- 空表
{} 编码为 {}(对象)。需要 [] 时用 qjson.empty_array_mt 强制(现有行为,保留)。
稀疏数组上限
沿用 lua-cjson 默认值(ratio = 2、safe = 10):当 max > safe 且
max > count * ratio 时,编码失败并报 "excessively sparse array" 类错误。
{[1]="a",[5]="e"} — 上限内 -> ["a",null,null,null,"e"]
{[1]=1,[3]=3} — 上限内 -> [1,null,3]
{[1]=1,[1000]=2} — 超限 -> 拒绝(lua-cjson:"Cannot serialise table: excessively sparse array")
应与 lua-cjson 对齐的差异
| ID |
类别 |
示例 |
lua-cjson 输出 |
qjson 当前 |
qjson 目标 |
| D01 |
顶层 nil |
encode(nil) |
null |
拒绝(unsupported value type: nil) |
编码为 null |
| D06 |
稀疏数组(上限内) |
{[1]="a",[5]="e"} |
["a",null,null,null,"e"] |
拒绝(数字键) |
["a",null,null,null,"e"] |
| D07 |
带洞数组 |
{[1]=1,[3]=3} |
[1,null,3] |
拒绝(数字键) |
[1,null,3] |
| D08 |
混合 table |
{1, a=2} |
{"1":1,"a":2} |
拒绝(数字键) |
对象,数字键字符串化 |
| D09 |
对象 + 正整数键 |
{name="value",[2]="two"} |
{"2":"two","name":"value"} |
拒绝(数字键) |
数字键字符串化 |
| D10 |
对象 + 零键 |
{[0]="zero"} |
{"0":"zero"} |
拒绝(数字键) |
数字键字符串化 |
| D11 |
对象 + 负数键 |
{[-1]="neg"} |
{"-1":"neg"} |
拒绝(数字键) |
数字键字符串化 |
| D12 |
对象 + 浮点键 |
{[1.5]="half"} |
{"1.5":"half"} |
拒绝(数字键) |
数字键字符串化 |
| D15 |
循环对象 |
t.self=t |
Cannot serialise, excessive nesting (1001) |
qjson.encode: max depth exceeded |
拒绝,文案改用 Cannot serialise, excessive nesting |
| D16 |
循环数组 |
t[1]=t |
Cannot serialise, excessive nesting (1001) |
qjson.encode: max depth exceeded |
拒绝,文案改用 Cannot serialise, excessive nesting |
说明:
- D01 将顶层
nil 从拒绝改为 null,这是有意的行为变更,移除了原先"误传 nil"的防护。
- D06/D07 遵循上面的数组规则 + 稀疏上限;D08–D12 遵循对象规则(数字键变为 JSON 字符串键)。
- D15/D16:两个编码器都用深度上限,默认均为 1000(qjson
ENCODE_MAX_DEPTH = 1000;
lua-cjson 报深度 1001)。qjson 的深度错误文案改用 lua-cjson 的主体文案
Cannot serialise, excessive nesting(不复刻 cjson 末尾的深度计数 (N),那是其内部细节);
验收按该主体文案匹配。
- JSON 对象成员顺序无意义;比较时 decode 回结构再比,或使用与顺序无关的断言。
应保留 qjson 行为的差异
| ID |
类别 |
示例 |
lua-cjson |
需保留的 qjson 行为 |
| D13 |
clean qjson lazy object |
qjson.decode('{"a":1}') |
静默误编码(如 [null,null]),proxy 数据经 rawget 不可见 |
原样切取原始 JSON 字节透传 |
| D14 |
dirty qjson lazy object |
mutation 后的 lazy object |
静默误编码(如 [null,null]) |
编码原始内容 + mutation,并保留键顺序 |
这些是 qjson 有意的扩展,必须以回归测试覆盖。注意 lua-cjson 不会拒绝 qjson proxy ——
它会静默产出错误结果并丢失数据,这正是 qjson 需要原生 proxy 编码的原因。
共享拒绝项
两个编码器都拒绝;qjson 须继续拒绝,文案不要求一致:
- 顶层
function、thread;
- 除 null sentinel 之外的
userdata;
- 所有
cdata,含 int64_t/uint64_t、double、struct、pointer —— lua-cjson
拒绝一切 cdata("Cannot serialise cdata: type not supported"),qjson 同样拒绝
("unsupported value type: cdata")。int64/uint64 cdata 编码不在本 issue 范围;
- table 值为上述不支持类型;
- table 键既非 string 也非 number(boolean、table、function、thread、userdata、cdata)。
不在本 issue 范围
- lua-cjson 配置函数(
encode_sparse_array、encode_max_depth、encode_number_precision、
encode_invalid_numbers、encode_keep_buffer)。qjson 采用与 lua-cjson 默认值对齐的固定
默认:encode 最大深度 1000、数值格式 %.14g、非有限数(NaN/Inf)拒绝。
- int64/uint64 cdata 编码(保持拒绝,见上面的共享拒绝项)。
需补充的测试覆盖
新增一个 Lua busted spec,分三段:
- lua-cjson 兼容编码行为 —— D01、D06–D12、D15–D16。通过 decode 双方输出再比较(顺序可能不同)。
包含数组规则的边界用例:{[2]="b"} -> [null,"b"]、{[1]="a",[2]="b",x="c"} -> 对象、
以及一个超限稀疏数组(如 {[1]=1,[1000]=2})被拒绝。循环输入断言双方都拒绝,且 qjson 错误消息
包含 Cannot serialise, excessive nesting。
- qjson 编码扩展 —— D13–D14。断言 qjson 成功并输出期望 JSON(clean 透传;dirty 重编码并保留键顺序)。
记录 lua-cjson 会静默误编码该 proxy(而非拒绝),以此说明这一有意差异。
- 共享不支持输入的拒绝 —— function/thread/userdata/**任意 cdata(含 int64/uint64)**值,
以及非 string 非 number 的 table 键。断言 qjson 拒绝;可选地断言 lua-cjson 也拒绝。
验收标准
qjson.encode 在 D01、D06–D12、D15–D16 上与 lua-cjson 一致(遵循上面的数组/对象规则与稀疏上限)。
- D15/D16 的循环/超深拒绝,qjson 错误消息采用 lua-cjson 主体文案
Cannot serialise, excessive nesting。
qjson.encode 对 D13–D14 保留 qjson lazy-proxy 行为。
- 所有 cdata(含 int64/uint64)及其它不支持类别保持拒绝。
- Lua 测试可通过现有 Makefile 测试路径运行。
- 现有的 qjson lazy encode、mutation、depth 与 property 测试继续通过。
背景
qjson.encode目标是成为常见lua-cjsonencode 用法的平滑迁移路径(drop-in),同时保留 lua-cjson 无法提供的一项优势:
通过原样切取原始 JSON 字节输出,这同时无损保留了大整数)。
本 issue 定义目标行为与需补充的测试覆盖,具体实现另行跟踪。所有对比的参照编码器为
vendored
lua-cjson(基于 OpenRestycjson2.1.0.11 实测核对)。行为策略
lua-cjson语义。数组 vs 对象 判定规则(核心)
显式写明,因为仅凭样例无法确定边界:
>= 1)时,table 编码为 JSON 数组。数组跨度为1..max,缺失的下标输出为null。即使缺少下标1也成立:{[2]="b"}->[null,"b"]。{[1]="a",[2]="b",x="c"}->{"1":"a","2":"b","x":"c"}。{}编码为{}(对象)。需要[]时用qjson.empty_array_mt强制(现有行为,保留)。稀疏数组上限
沿用 lua-cjson 默认值(
ratio = 2、safe = 10):当max > safe且max > count * ratio时,编码失败并报 "excessively sparse array" 类错误。{[1]="a",[5]="e"}— 上限内 ->["a",null,null,null,"e"]{[1]=1,[3]=3}— 上限内 ->[1,null,3]{[1]=1,[1000]=2}— 超限 -> 拒绝(lua-cjson:"Cannot serialise table: excessively sparse array")应与 lua-cjson 对齐的差异
nilencode(nil)nullunsupported value type: nil)null{[1]="a",[5]="e"}["a",null,null,null,"e"]["a",null,null,null,"e"]{[1]=1,[3]=3}[1,null,3][1,null,3]{1, a=2}{"1":1,"a":2}{name="value",[2]="two"}{"2":"two","name":"value"}{[0]="zero"}{"0":"zero"}{[-1]="neg"}{"-1":"neg"}{[1.5]="half"}{"1.5":"half"}t.self=tCannot serialise, excessive nesting (1001)qjson.encode: max depth exceededCannot serialise, excessive nestingt[1]=tCannot serialise, excessive nesting (1001)qjson.encode: max depth exceededCannot serialise, excessive nesting说明:
nil从拒绝改为null,这是有意的行为变更,移除了原先"误传 nil"的防护。ENCODE_MAX_DEPTH = 1000;lua-cjson 报深度 1001)。qjson 的深度错误文案改用 lua-cjson 的主体文案
Cannot serialise, excessive nesting(不复刻 cjson 末尾的深度计数(N),那是其内部细节);验收按该主体文案匹配。
应保留 qjson 行为的差异
qjson.decode('{"a":1}')[null,null]),proxy 数据经 rawget 不可见[null,null])这些是 qjson 有意的扩展,必须以回归测试覆盖。注意 lua-cjson 不会拒绝 qjson proxy ——
它会静默产出错误结果并丢失数据,这正是 qjson 需要原生 proxy 编码的原因。
共享拒绝项
两个编码器都拒绝;qjson 须继续拒绝,文案不要求一致:
function、thread;userdata;cdata,含int64_t/uint64_t、double、struct、pointer —— lua-cjson拒绝一切 cdata("Cannot serialise cdata: type not supported"),qjson 同样拒绝
("unsupported value type: cdata")。int64/uint64 cdata 编码不在本 issue 范围;
不在本 issue 范围
encode_sparse_array、encode_max_depth、encode_number_precision、encode_invalid_numbers、encode_keep_buffer)。qjson 采用与 lua-cjson 默认值对齐的固定默认:encode 最大深度 1000、数值格式
%.14g、非有限数(NaN/Inf)拒绝。需补充的测试覆盖
新增一个 Lua busted spec,分三段:
包含数组规则的边界用例:
{[2]="b"}->[null,"b"]、{[1]="a",[2]="b",x="c"}-> 对象、以及一个超限稀疏数组(如
{[1]=1,[1000]=2})被拒绝。循环输入断言双方都拒绝,且 qjson 错误消息包含
Cannot serialise, excessive nesting。记录 lua-cjson 会静默误编码该 proxy(而非拒绝),以此说明这一有意差异。
以及非 string 非 number 的 table 键。断言 qjson 拒绝;可选地断言 lua-cjson 也拒绝。
验收标准
qjson.encode在 D01、D06–D12、D15–D16 上与 lua-cjson 一致(遵循上面的数组/对象规则与稀疏上限)。Cannot serialise, excessive nesting。qjson.encode对 D13–D14 保留 qjson lazy-proxy 行为。