第 18 章 / 共 51 章
卷三 · 协议开发
JSON 与 xvalue:带所有权的动态值
API 时代的通用语。xvalue 是引用计数的动态值树——本章的核心只有一件事:搞清每个 xvalue* 归谁、何时 Release。
本章导读
站位:xvalue 贯穿全书——Custom 旋钮(ch09)、Swap 交接(ch28)、配置树都是它。本章建立的所有权心智一次投入全书回报。
18.1 引入
C 没有原生 JSON 类型,常见做法是绑定第三方库 + 手拼字符串。XServer 的做法更彻底:xrt 内建一套引用计数的动态值系统(xvalue,116 个 API),JSON 只是它的序列化格式之一(XSON 是另一种)。它同时服务三个场景:配置树(Custom)、运行时数据交换(Swap)、协议序列化(JSON)。
18.2 构造与所有权
构造的真实写法(demo basic.h 节选)实机验证出处:demo-single/route_http/basic.h
static void Handle_JSON(XS_HttpReq* pReq, const RouteParamHTTP* arrParam, uint32 iParamCount)
{
const XS_HostInfo* pHost = pReq->host;
xvalue* pObject = xrtValueObject(); /* 你拥有 1 个引用 */
(void)arrParam; (void)iParamCount;
xrtValueObjectSetNew(pObject, XRT_STR_LITERAL("server"),
xrtValueString(XRT_STR_LITERAL("xs3 demo")));
xrtValueObjectSetNew(pObject, XRT_STR_LITERAL("ok"),
xrtValueBool(true));
xrtValueObjectSetNew(pObject, XRT_STR_LITERAL("host"),
xrtValueString(xrtStrView((pHost->Name ? pHost->Name : "?"))));
(void)ReplyJSON(pReq, 200, pObject); /* ReplyJSON 只借用 */
xrtValueRelease(pObject); /* 你释放你的引用 */
}
刚才发生了什么——所有权规则一目了然:
- 构造函数(Object/Array/String/Int/Bool)返回你持有的引用
xrtValueObjectSetNew的 New 后缀:把值引用转交容器——你不再需要(也不能)Release 被放入的值- Release 减引用计数;归零销毁整棵子树(子值随容器走)
XRT_STR_LITERAL用于字面量视图(编译期长度);运行时字符串用xrtStrView指长度
实机输出
$ curl -s http://127.0.0.1:9080/json {"server":"xs3 demo","ok":true,"host":"app"}
18.3 解析与访问
解析请求 JSON(demo http.h 模式)出处:demo-single/modules/http.h(模式节选)
/* 从请求体视图解析(配合 ch16 的 body 读取)*/
xvalue* pRoot = xrtJsonParse(tBodyView);
if ( pRoot == NULL ) {
(void)ReplyText(pReq, 400, "bad json");
return XS_OK;
}
/* 访问:Get 返回借用(不增引用,读完不用 Release)*/
xvalue* pName = xrtValueObjectGet(pRoot, "name");
if ( pName != NULL ) {
/* 取标量值(类型不符返回默认)*/
int64 iId = xrtValueInt64(xrtValueObjectGet(pRoot, "id"));
}
/* 序列化:Stringify 返回 str(调用方释放)*/
str sOut = xrtJsonStringify(pRoot, ...);
xrtValueRelease(pRoot); /* 解析者释放树 */
/* ... 使用 sOut ... */
注意 Get 是借用、Parse 是持有——两件事最容易混淆:Get 到的指针不用 Release,但也不能活得比树久。
18.4 ReplyJSON 模式
demo 的 ReplyJSON 注释只有一句:"只借用 pObject,调用方仍负责 xrtValueRelease"——这就是模块边界的所有权声明(第 5 章三要素)。序列化在 Reply 内部完成(Stringify → ReplyRaw),handler 保持"构造 → 交给 Reply → 释放"的三段式,永远直白。
18.5 契约
- 构造线:构造返回持有引用;SetNew 转交;Release 配对。
- 访问线:ObjectGet/ArrayGet 返回借用——不 Release、不超树生命周期。
- 序列化线:Stringify 返回 str 由调用方释放;Parse 返回树由解析者释放。
常见错误
Release 了 Get 的结果
- 症状
- 随机的树损坏 / 崩溃,通常在并发下才显形。
- 原因
- Get 是借用(不增引用)——对它 Release 是在销毁不属于自己的引用。口诀:谁 Create/Parse 谁 Release,Get 永不 Release。
SetNew 之后又 Release 子值
- 症状
- 提前释放导致容器内悬空。
- 原因
- New 后缀 = 所有权转交容器。之后子值归树管——你手里的引用已经交出去了。
✗ 双重管理
xvalue* pV = xrtValueInt(1);
xrtValueObjectSetNew(pObj, "k", pV);
xrtValueRelease(pV); /* 交了又放 */✓ 正确
xrtValueObjectSetNew(pObj, "k",
xrtValueInt(1)); /* 内联最常见 */编程练习
基础嵌套构造
构造 {"a":1,"list":[{"x":true},...]} 三层嵌套并 ReplyJSON 返回。
验收:输出与 jq 解析一致;无泄漏(内存报告核对)。
进阶POST 校验器
实现 /api/echo-json:解析 body,校验必填字段(缺失 400 + 错误清单数组),通过则回显。
验收:缺两个字段时返回两个错误项;畸形 JSON 返回 400。
挑战所有权压力测试
写一个 1000 次的构造-解析-释放循环,配合 xrt 内存报告(xrt 教程 ch06)验证引用计数归零。
验收:循环前后活动分配数一致。
小结
| 要点 | 结论 |
|---|---|
| xvalue | 引用计数动态值树;JSON 只是序列化格式之一 |
| 所有权口诀 | 谁 Create/Parse 谁 Release;Get 借用不 Release |
| SetNew | 所有权转交容器,之后归树管 |
| ReplyJSON | 只借用——模块边界要写所有权注释 |
本章涉及的 API 参考
xrt 延伸阅读
- xrt 参考 · value — 116 个构造/访问/集合 API 全集
- xrt 参考 · xson — 流式序列化变体