JSON & xvalue: dynamic values with ownership
The lingua franca of the API era. xvalue is a reference-counted tree of dynamic values — this chapter has one core mission: knowing who owns every xvalue* and when to Release.
Overview
18.1 Intro
C has no native JSON type; the usual route is binding a third-party library plus hand-built strings. XServer goes further: xrt ships a reference-counted dynamic value system (xvalue, 116 APIs); JSON is merely one of its serialization formats (XSON is another). It serves three scenes at once: the config tree (Custom), runtime data exchange (Swap), and protocol serialization (JSON).
18.2 Construction & ownership
static void Handle_JSON(XS_HttpReq* pReq, const RouteParamHTTP* arrParam, uint32 iParamCount)
{
const XS_HostInfo* pHost = pReq->host;
xvalue* pObject = xrtValueObject(); /* you own 1 reference */
(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 only borrows */
xrtValueRelease(pObject); /* you release your reference */
}
What just happened — the ownership rules at a glance:
- Constructors (Object/Array/String/Int/Bool) return a reference you hold
- The New suffix of
xrtValueObjectSetNew: it transfers the value's reference to the container — you no longer need (and must not) Release the inserted value - Release decrements the count; at zero the whole subtree is destroyed (children go with the container)
XRT_STR_LITERALis for literal views (compile-time length); runtime strings usexrtStrViewwith an explicit length
$ curl -s http://127.0.0.1:9080/json {"server":"xs3 demo","ok":true,"host":"app"}
18.3 Parsing & access
/* parse from the body view (with ch16's body reading) */
xvalue* pRoot = xrtJsonParse(tBodyView);
if ( pRoot == NULL ) {
(void)ReplyText(pReq, 400, "bad json");
return XS_OK;
}
/* access: Get returns a borrow (no ref added; no Release after reading) */
xvalue* pName = xrtValueObjectGet(pRoot, "name");
if ( pName != NULL ) {
/* take scalar values (type mismatch returns the default) */
int64 iId = xrtValueInt64(xrtValueObjectGet(pRoot, "id"));
}
/* serialize: Stringify returns str (caller frees) */
str sOut = xrtJsonStringify(pRoot, ...);
xrtValueRelease(pRoot); /* the parser frees the tree */
/* ... use sOut ... */
Note the easiest pair to confuse: Get borrows, Parse owns — a pointer from Get needs no Release, but it must not outlive the tree.
18.4 The ReplyJSON pattern
The demo's ReplyJSON carries a single comment: "only borrows pObject; the caller still owns the xrtValueRelease" — an ownership declaration at the module boundary (chapter 5's three traits). Serialization happens inside the Reply (Stringify → ReplyRaw), so handlers keep the plain three-step "construct → hand to Reply → release".
18.5 Contract
- Construction: constructors return owned references; SetNew transfers; Release pairs.
- Access: ObjectGet/ArrayGet return borrows — never Release, never outlive the tree.
- Serialization: Stringify's str is freed by the caller; Parse's tree is freed by the parser.
Common mistakes
- Symptom
- Random tree corruption / crashes, often surfacing only under concurrency.
- Cause
- Get borrows (no reference added) — Releasing it destroys a reference you don't own. The mantra: whoever Creates/Parses Releases; Get never Releases.
- Symptom
- Premature free leaves a dangling entry in the container.
- Cause
- The New suffix = ownership transferred to the container. The child belongs to the tree afterwards — your reference has already been handed over.
xvalue* pV = xrtValueInt(1);
xrtValueObjectSetNew(pObj, "k", pV);
xrtValueRelease(pV); /* transferred, then freed */xrtValueObjectSetNew(pObj, "k",
xrtValueInt(1)); /* inlining is the norm */Exercises
Build {"a":1,"list":[{"x":true},...]} three levels deep and return it with ReplyJSON.
Implement /api/echo-json: parse the body, validate required fields (missing → 400 + an error-list array), echo on success.
Write a 1000-iteration construct-parse-release loop and verify reference counts hit zero with xrt's memory report (xrt book ch. 06).
Summary
| Point | Conclusion |
|---|---|
| xvalue | A reference-counted dynamic value tree; JSON is just one serialization |
| Ownership mantra | whoever Creates/Parses Releases; Get borrows, never Releases |
| SetNew | ownership transfers to the container; the tree owns it afterwards |
| ReplyJSON | borrows only — module boundaries declare ownership |
- xrt reference · value — all 116 construction/access/collection APIs
- xrt reference · xson — the streaming serialization variant