Глава 18 / 51
Том 3 · Разработка протоколов

JSON и xvalue: динамические значения с владением

Лингва франка эпохи API. xvalue — считающее ссылки дерево динамических значений; у главы одна центральная задача: понять, кому принадлежит каждый xvalue* и когда его Release.

Обзор

Позиционирование: xvalue проходит через всю книгу — ручки Custom (гл. 09), передача Swap (гл. 28), дерево конфигурации — всё это он. Модель владения, построенная здесь, окупается всей книгой.

18.1 Введение

В C нет нативного типа JSON; обычный путь — сторонняя библиотека плюс ручная сборка строк. XServer идёт дальше: в xrt встроена считающая ссылки система динамических значений (xvalue, 116 API); JSON — лишь один из форматов её сериализации (XSON — другой). Она обслуживает сразу три сцены: дерево конфигурации (Custom), обмен данными в рантайме (Swap) и сериализацию протоколов (JSON).

18.2 Построение и владение

Построение как оно пишется (фрагмент 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) возвращают ссылку в вашем владении
  • Суффикс 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 Разбор и доступ

Разбор JSON запроса (паттерн http.h демо)источник: demo-single/modules/http.h (фрагмент паттерна)
/* разбор из представления тела (вместе с чтением тела из гл. 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 — разборщик.

Частые ошибки

Release результата Get
Симптом
Случайная порча дерева / падения, часто вылезают только под конкурентностью.
Причина
Get заимствует (ссылку не добавляет) — Release по нему уничтожает не вашу ссылку. Мантра: кто Create/Parse, тот и Release; Get никогда не Release.
Release дочернего значения после SetNew
Симптом
Преждевременное освобождение оставляет в контейнере висячую запись.
Причина
Суффикс 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: разобрать тело, проверить обязательные поля (нет → 400 + массив ошибок), при успехе эхо.

Приёмка: два отсутствующих поля дают два пункта ошибок; битый JSON даёт 400.
ВызовСтресс-тест владения

Напишите цикл из 1000 итераций построение-разбор-освобождение и через отчёт памяти xrt (учебник xrt, гл. 06) убедитесь в обнулении счётчиков.

Приёмка: число активных распределений до и после цикла совпадает.

Итоги

ТезисВывод
xvalueСчитающее ссылки дерево динамических значений; JSON — лишь одна сериализация
Мантра владениякто Create/Parse, тот Release; Get заимствует, Release никогда
SetNewвладение переходит контейнеру, дальше дереву
ReplyJSONтолько заимствует — на границе модуля объявляйте владение
API, затронутые в главе
Что дальше по xrt