Chapter 18 / 51
Volume 3 · Protocol Development

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

Positioning: xvalue runs through the whole book — Custom knobs (ch. 09), Swap handover (ch. 28), the config tree are all it. The ownership model built here pays off across the entire book.

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

Construction as really written (demo basic.h excerpt)verified on a real machinesource: 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();          /* 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_LITERAL is for literal views (compile-time length); runtime strings use xrtStrView with an explicit length
Real output
$ curl -s http://127.0.0.1:9080/json
{"server":"xs3 demo","ok":true,"host":"app"}

18.3 Parsing & access

Parsing request JSON (the demo http.h pattern)source: demo-single/modules/http.h (pattern excerpt)
/* 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

Releasing the result of Get
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.
Releasing a child value after SetNew
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.
✗ Double management
xvalue* pV = xrtValueInt(1);
xrtValueObjectSetNew(pObj, "k", pV);
xrtValueRelease(pV);   /* transferred, then freed */
✓ Right
xrtValueObjectSetNew(pObj, "k",
    xrtValueInt(1));   /* inlining is the norm */

Exercises

BasicNested construction

Build {"a":1,"list":[{"x":true},...]} three levels deep and return it with ReplyJSON.

Acceptance: the output parses identically under jq; zero leaks (checked against the memory report).
AdvancedA POST validator

Implement /api/echo-json: parse the body, validate required fields (missing → 400 + an error-list array), echo on success.

Acceptance: two missing fields yield two error items; malformed JSON yields 400.
ChallengeAn ownership stress test

Write a 1000-iteration construct-parse-release loop and verify reference counts hit zero with xrt's memory report (xrt book ch. 06).

Acceptance: active allocation counts identical before and after the loop.

Summary

PointConclusion
xvalueA reference-counted dynamic value tree; JSON is just one serialization
Ownership mantrawhoever Creates/Parses Releases; Get borrows, never Releases
SetNewownership transfers to the container; the tree owns it afterwards
ReplyJSONborrows only — module boundaries declare ownership
API references in this chapter
xrt further reading