JSON и xvalue: динамические значения с владением
Лингва франка эпохи API. xvalue — считающее ссылки дерево динамических значений; у главы одна центральная задача: понять, кому принадлежит каждый xvalue* и когда его Release.
Обзор
18.1 Введение
В C нет нативного типа JSON; обычный путь — сторонняя библиотека плюс ручная сборка строк. XServer идёт дальше: в xrt встроена считающая ссылки система динамических значений (xvalue, 116 API); JSON — лишь один из форматов её сериализации (XSON — другой). Она обслуживает сразу три сцены: дерево конфигурации (Custom), обмен данными в рантайме (Swap) и сериализацию протоколов (JSON).
18.2 Построение и владение
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) возвращают ссылку в вашем владении
- Суффикс New у
xrtValueObjectSetNew: передаёт ссылку на значение контейнеру — вам больше не нужно (и нельзя) Release вставленного значения - Release уменьшает счётчик; при нуле уничтожается всё поддерево (потомки уходят вместе с контейнером)
XRT_STR_LITERAL— для литеральных представлений (длина на компиляции); строки рантайма черезxrtStrViewс явной длиной
$ curl -s http://127.0.0.1:9080/json {"server":"xs3 demo","ok":true,"host":"app"}
18.3 Разбор и доступ
/* разбор из представления тела (вместе с чтением тела из гл. 16) */
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
Комментарий ReplyJSON демо — одна фраза: «лишь заимствует pObject; xrtValueRelease остаётся за вызывающим» — это и есть объявление владения на границе модуля (три признака из главы 5). Сериализация завершается внутри Reply (Stringify → ReplyRaw), и обработчик держит прямой трёхтакт «построил → отдал Reply → освободил».
18.5 Контракт
- Построение: конструкторы возвращают владение; SetNew передаёт; Release в паре.
- Доступ: ObjectGet/ArrayGet возвращают заимствование — без Release и не дольше дерева.
- Сериализация: str из Stringify освобождает вызывающий; дерево из Parse — разборщик.
Частые ошибки
- Симптом
- Случайная порча дерева / падения, часто вылезают только под конкурентностью.
- Причина
- Get заимствует (ссылку не добавляет) — Release по нему уничтожает не вашу ссылку. Мантра: кто Create/Parse, тот и Release; Get никогда не Release.
- Симптом
- Преждевременное освобождение оставляет в контейнере висячую запись.
- Причина
- Суффикс New = передача владения контейнеру. Потомок принадлежит дереву — ваша ссылка уже отдана.
xvalue* pV = xrtValueInt(1);
xrtValueObjectSetNew(pObj, "k", pV);
xrtValueRelease(pV); /* передал и освободил */xrtValueObjectSetNew(pObj, "k",
xrtValueInt(1)); /* обычно инлайном */Упражнения
Постройте {"a":1,"list":[{"x":true},...]} в три уровня и верните через ReplyJSON.
Реализуйте /api/echo-json: разобрать тело, проверить обязательные поля (нет → 400 + массив ошибок), при успехе эхо.
Напишите цикл из 1000 итераций построение-разбор-освобождение и через отчёт памяти xrt (учебник xrt, гл. 06) убедитесь в обнулении счётчиков.
Итоги
| Тезис | Вывод |
|---|---|
| xvalue | Считающее ссылки дерево динамических значений; JSON — лишь одна сериализация |
| Мантра владения | кто Create/Parse, тот Release; Get заимствует, Release никогда |
| SetNew | владение переходит контейнеру, дальше дереву |
| ReplyJSON | только заимствует — на границе модуля объявляйте владение |
- справочник xrt · value — все 116 API построения/доступа/коллекций
- справочник xrt · xson — потоковый вариант сериализации