Ориентация главы
JSON не выражает слишком многого: двоичные данные требуют ручного Base64, метки времени — строковых договорённостей, множества и отображения с целыми ключами вырождаются в массивы; каждый зазор — ещё один слой собственных конвенций, каждый слой — мина совместимости. XLON — строгое надмножество JSON: легальный JSON автоматически легален как XLON, плюс четыре первоклассных типа — bytes("...") (инлайновый Base64), time("...") (метка времени ISO), set[...] (множество с дедупликацией) и intmap{...} (отображение с целыми ключами). Парсер — тот же, что в главе 31 (событийные обратные вызовы различают по полю типа; расширенные типы больше не вырождаются в строки), сериализация записывает все четыре типа как есть — полный круговой проход без стоимости конвенций. Когда выбирать XLON и когда держаться JSON — глава даёт правила решения.
33.1引入
Истинная форма одной внутренней конфигурации: приватный ключ сертификата — двоичные данные (в JSON только Base64-строка, потребитель обязан знать «эту строку надо декодировать»); срок действия — метка времени (строкой или числом? чей часовой пояс?); допустимые порты — множество (а повторы в JSON-массиве? потребитель сам дедуплицирует?); параметры соединения — отображение номер порта → описание (ключи JSON-объекта только строки — кто конвертирует между "80" и 80?). Четыре поля — четыре конвенции, документация на две страницы, и всё равно кто-то ошибается.
Ответ XLON — сделать эти четыре формы первоклассными типами: bytes("AAEC...") несёт собственное объявление кодирования, time("2026-07-31T00:00:00Z") унифицирует UTC в миллисекундах, set[80, 443] дедуплицирует самой семантикой, intmap{80: "http"} отображает с родными целыми ключами — у xvalue дерева значений (глава 30) эти четыре типа уже есть, XLON лишь не даёт сериализации расплющивать их. Дизайн надмножества гарантирует плавность: существующие JSON-инструменты работают как раньше, JSON-части документа XLON легальны для любого JSON-парсера.
33.2概念
Четыре расширенных типа
Все четыре типа имеют соответствия в дереве значений (глава 30): значения Bytes/Time, контейнеры set/intmap — сериализация XLON это просто полное отображение «дерево значений ↔ текст» без потери информации о типах. Поток событий (SAX) тоже различает: поле Type у xxlonevent прямо помечает расширенный тип — потребителю не нужно гадать, «не спрятано ли время в этой строке».
Смысл строгого надмножества
Три свойства, каждое стоит проговорить отдельно. Прямая совместимость: любой легальный JSON-документ легален как XLON — переименовать существующие конфигурации .json в .xlon стоит нуля. Обратная несовместимость: документ XLON с расширенными типами нелегален для чистого JSON-парсера (bytes( не JSON-синтаксис) — XLON только для каналов, «где оба конца — XRT или договорились об XLON». Взаимонепричастность: парсер один и тот же, разбор идёт по фактическому содержимому — дерево из JSON-документа не содержит расширенных типов, из XLON-документа может; конфигурационной системе не нужно вести два кодовых пути под два формата.
Правила выбора против JSON
| Сценарий | Выбор | Причина |
|---|---|---|
| Открытый внешний API | JSON | лингва франка, любой клиент разберёт |
| Внутренние конфигурации | XLON | двоичные данные/время/множества в родном виде, ноль конвенций |
| Данные между внутренними сервисами | XLON | оба конца подконтрольны, полный круг типов |
| Конфигурации для ручного редактирования | JSON или XLON | ISO-строку человеку писать приятнее числа миллисекунд |
Центральная переменная правила — подконтролен ли получатель: подконтролен → XLON с полными типами; неподконтролен → JSON ради совместимости. Смешанная форма тоже обычна: поля открытого API — типы JSON, промежуточное представление внутренней обработки — дерево значений (в нём всегда полные типы): преобразование на границе, внутри без компромиссов.
Позиционирование ошибок и строгость
XLON наследует всю строгость главы 31: три шлюза границ ресурсов, отвержение дубликатов ключей, ошибки со строкой и столбцом. Ошибки разбора расширенных типов также локализуются до символа — нелегальный месяц в time("2026-13-01...") указывается прямо в позиции.
33.3示例
Полная программа: круговой проход всех типов
Из образца репозитория examples/data/xlon/main.c — четыре расширенных типа одной записью-чтением:
*/ #include <inttypes.h> #include <stdio.h> #include <xrt.h> */ static xxlonvisitaction printXlonEvent( const xxlonevent* pEvent, ptr pUserData ) { (void)pUserData; if ( pEvent->Type == XXLON_EVENT_BYTES ) { printf("bytes = %u\n", (unsigned)pEvent->Value.Bytes.Size); } else if ( pEvent->Type == XXLON_EVENT_TIME ) { printf("time = %" PRId64 "\n", (int64)pEvent->Value.Time); } return XXLON_VISIT_NEXT; } int main(void) { /* 四种扩展类型各出现一次的输入文档。 */ static const char sInput[] = "{\"blob\":bytes(\"AAEC/w==\")," "\"updated\":time(\"2026-07-31T08:00:00+08:00\")," "\"roles\":set[\"reader\",\"writer\"]," "\"ports\":intmap{80:\"http\",443:\"https\"}}"; xxlonreadconfig ReadConfig; xxlonwriteconfig WriteConfig; xxlonwriter* pWriter; xvalue* pRoot; str sText; size_t iSize; /* ---- 1) DOM 往返:解析 → 美化输出(time 已归一为 UTC Z 后缀)---- */ pRoot = xrtXlonParse((xstrview){ sInput, sizeof(sInput) - 1u }); if ( pRoot == NULL ) { return 1; } sText = xrtXlonStringify(pRoot, true, &iSize); xrtValueRelease(pRoot); if ( sText == NULL ) { return 2; } printf("%.*s\n", (int)iSize, sText); xrtFree(sText); /* ---- 2) SAX:同一输入走事件流,只处理扩展类型 ---- */ xrtXlonReadConfigInit(&ReadConfig); if ( xrtXlonVisit( (xstrview){ sInput, sizeof(sInput) - 1u }, &ReadConfig, printXlonEvent, NULL ) != XXLON_VISIT_DONE ) { return 3; } */ xrtXlonWriteConfigInit(&WriteConfig); pWriter = xrtXlonWriterCreate(&WriteConfig); if ( (pWriter == NULL) || !xrtXlonWriterObject(pWriter) || !xrtXlonWriterName(pWriter, XRT_STR_LITERAL("code")) || !xrtXlonWriterInt(pWriter, 200) || !xrtXlonWriterName(pWriter, XRT_STR_LITERAL("tags")) || !xrtXlonWriterSet(pWriter) || /* 开 set */ !xrtXlonWriterString(pWriter, XRT_STR_LITERAL("xrt")) || !xrtXlonWriterEnd(pWriter) || /* 闭 set */ !xrtXlonWriterEnd(pWriter) || /* 闭对象 */ !xrtXlonWriterFinish(pWriter) ) { xrtXlonWriterFree(pWriter); return 4; } sText = xrtXlonWriterTake(pWriter, &iSize); xrtXlonWriterFree(pWriter); if ( sText == NULL ) { return 5; } printf("%.*s\n", (int)iSize, sText); xrtFree(sText); return 0; }
Что только что произошло. (1) Сегмент сериализации: четыре типа дерева записаны как есть — bytes("AAEC/w==") с объявлением кодирования, time(...) ISO-строкой, set[...] и intmap{...} каждое своей скобочной семантикой. Информация о типах видна в тексте — читателю документа не нужна таблица конвенций. (2) Сегмент разбора: bytes = 4 — Base64 декодирован обратно в 4 двоичных байта; time = 1785456000000000 — ISO-строка нормализована в целое число миллисекунд UTC (мера xtime главы 3). (3) Последняя строка — компактная сериализация (без красоты): форма set["xrt"] в одну строку; машинный канал — компакт, диск для просмотра — красота, выбор тот же, что у JSON.
Полная программа: файловый круг и позиционирование ошибок
Из examples/data/xlon_tour/main.c:
*/ #include <stdio.h> #include <string.h> #include <xrt.h> /* 输出回调:累计字节数。 */ typedef struct examplesink { size_t iBytes; bool bOk; } examplesink; static bool exampleSink(xbytesview Data, ptr pUserData) { examplesink* pSink = (examplesink*)pUserData; pSink->iBytes += Data.Size; return pSink->bOk; } int main(void) { static const char* sFile = "xlon_tour_tmp.xlon"; static const char* sBad = "{\"a\":}"; xxlonreadconfig ReadConfig; xxlonwriteconfig WriteConfig; xxlonlocation Location; xvalue* pDom = NULL; xvalue* pFileDom = NULL; xvalue* pSub = NULL; xxlonwriter* pWriter = NULL; examplesink Sink; str sText = NULL; FILE* pOut = NULL; int iResult = 1; /* ---- Valid 正反 + Read 带配置。 ---- */ xrtXlonReadConfigInit(&ReadConfig); if ( xrtXlonValid((xstrview) { sBad, 6u }) || !xrtXlonValid(XRT_STR_LITERAL("[1,2,3]")) || ((pDom = xrtXlonRead(XRT_STR_LITERAL( "{\"n\":7}"), &ReadConfig)) == NULL) ) { goto Cleanup; } printf("xlon: valid +/- and read(config) ok\n"); /* ---- 错误定位:坏文档的 xrt.xlon 错误带文本位置。 ---- */ { const xerror* pError; if ( (xrtXlonRead((xstrview) { sBad, 6u }, &ReadConfig) != NULL) ) { goto Cleanup; } pError = xrtGetError(); if ( (pError == NULL) || !xrtXlonErrorLocation(pError, &Location) || (Location.Line != 1u) || (Location.Column == 0u) ) { goto Cleanup; } } printf("xlon: error location line=1 ok\n"); /* ---- 文件双形态:手写落盘 → ParseFile/ReadFile 读回。 ---- */ pOut = fopen(sFile, "wb"); if ( (pOut == NULL) || (fwrite("{\"f\":1.5}", 1u, 9u, pOut) != 9u) ) { goto Cleanup; } fclose(pOut); pOut = NULL; pFileDom = xrtXlonParseFile(sFile); if ( (pFileDom == NULL) || (xrtXlonReadFile(sFile, &ReadConfig) == NULL) ) { goto Cleanup; } /* WriteFile/StringifyFile:原子替换后可再读回。 */ xrtXlonWriteConfigInit(&WriteConfig); if ( !xrtXlonWriteFile(sFile, pDom, &WriteConfig) || !xrtXlonStringifyFile(sFile, pDom, true) || (xrtXlonParseFile(sFile) == NULL) ) { goto Cleanup; } printf("xlon: file roundtrip parse/read -> write/stringify ok\n"); /* ---- 同步写出:Write 把 Value 树提交给回调。 ---- */ memset(&Sink, 0, sizeof(Sink)); Sink.bOk = true; if ( !xrtXlonWrite(pDom, &WriteConfig, exampleSink, (ptr)&Sink) || (Sink.iBytes != 7u) ) { /* {"n":7} 恰七字节。 */ goto Cleanup; } printf("xlon: sync write via callback ok\n"); /* ---- 增量写入器:CreateSink + 全值域方法。 ---- */ memset(&Sink, 0, sizeof(Sink)); Sink.bOk = true; pWriter = xrtXlonWriterCreateSink(&WriteConfig, exampleSink, (ptr)&Sink); pSub = xrtXlonParse(XRT_STR_LITERAL("[true]")); if ( (pWriter == NULL) || (pSub == NULL) || /* 顶层数组:混排七种值域 + 子树 + 标签。 */ !xrtXlonWriterArray(pWriter) || !xrtXlonWriterNull(pWriter) || !xrtXlonWriterBool(pWriter, true) || !xrtXlonWriterUInt(pWriter, 12u) || !xrtXlonWriterFloat(pWriter, 0.5) || !xrtXlonWriterBytes(pWriter, (xbytesview) { (const uint8*)"xy", 2u }) || !xrtXlonWriterTime(pWriter, (xtime)1000000) || !xrtXlonWriterTag(pWriter, XRT_STR_LITERAL("base64"), XRT_STR_LITERAL("eHl6")) || !xrtXlonWriterValue(pWriter, pSub) || !xrtXlonWriterEnd(pWriter) || !xrtXlonWriterFinish(pWriter) ) { goto Cleanup; } /* 嵌套 intmap:整数键映射容器。 */ pWriter = (xrtXlonWriterFree(pWriter), NULL); pWriter = xrtXlonWriterCreateSink(&WriteConfig, exampleSink, (ptr)&Sink); if ( (pWriter == NULL) || !xrtXlonWriterIntMap(pWriter) || !xrtXlonWriterKey(pWriter, 7) || !xrtXlonWriterBool(pWriter, false) || !xrtXlonWriterEnd(pWriter) || !xrtXlonWriterFinish(pWriter) || (Sink.iBytes < 20u) ) { goto Cleanup; } printf("xlon: sink writer full value domain ok\n"); iResult = 0; Cleanup: if ( pOut != NULL ) { fclose(pOut); } xrtXlonWriterFree(pWriter); xrtValueRelease(pSub); xrtValueRelease(pFileDom); xrtValueRelease(pDom); xrtFree(sText); (void)remove(sFile); return iResult; }
Что только что произошло. (1) Валидация и чтение: проверка легальности документа XLON и чтение полей конфигурации одним заходом. (2) Позиционирование ошибок: подайте вход без запятой — line=1 сообщает точно; способность локализации JSON-а главы 31 унаследована дословно. (3) Файловый круг: разбор → чтение полей → запись обратно → сериализация, замкнутый контур подтверждает сохранность типов. (4) Запись через обратный вызов: вывод сериализации отдаётся кусками через обратный вызов — форма потоковой записи на диск или прямого подключения к sink, как у Writer-а главы 31. (5) Запись в sink покрывает всю область значений: скаляры и все четыре расширенных типа проходят через sink по очереди — полные типы выражает не только DOM; потоковая запись тоже не теряет типов. Пять «ok» вместе — доказательство: «у XLON тот же инженерный набор, что у JSON, только типов больше».
33.4契约
- Строгое надмножество: легальный JSON обязательно легальный XLON; парсер один; JSON-документы не содержат расширенных типов.
- Круг четырёх типов: bytes/time/set/intmap сериализуются и разбираются как есть, без стоимости конвенций.
- Нормализация времени:
time(...)разбирается в нормализованные миллисекунды UTC (мераxtime); сериализация пишет ISO 8601. - Правило выбора: получатель подконтролен → XLON с полными типами; неподконтролен → JSON ради совместимости; преобразование на границе, внутри без компромиссов.
- Наследованная строгость: три шлюза, отвержение дубликатов, строка-столбец ошибок — полностью согласовано с JSON.
- Компакт и красота: машинный канал — компакт, диск для просмотра — красота; правила выбора те же, что у JSON.
От примеров к инженерии: три хозяина XLON
Внутренние конфигурации (главная площадка): загрузка на старте → дерево значений → слияние главы 30 — двоичные ключи, сроки действия, множества прав и отображения портов всё первоклассно, потребительский код без преобразований. Это прямой целевой сценарий дизайна XLON. Внутренняя сервисная шина: сериализация сообщений между сервисами — оба конца XRT, полный круг типов стирает кодирующие слои с обеих сторон; в паре с очередью главы 21 выбор по каналу: передать дерево значений напрямую (без сериализации) или передать XLON-сериализацию (через процессы).Выход понижения на внешней границе: когда наружу нужен JSON — понижение по конвенции (хорошая форма ловушки 1); логика понижения сосредоточена в пограничном модуле, внутренние деревья значений хранят полные типы. Общая точка трёх хозяев: информация о типах внутри всегда первоклассна и понижается только на явно размеченной границе.
Контраст: одна конфигурация в двух записях
Запишите «конфигурацию сертификата» в обоих форматах — и различия выпрыгивают сами. Форма JSON: {"key": "AAEC...", "expire": "2026-07-31T00:00:00Z", "ports": [80, 443, 80]} — key требует документации, что это Base64; expire — конвенции ISO и часового пояса; массив ports с повторами, и потребитель должен дедуплицировать. Форма XLON: {"key": bytes("AAEC..."), "expire": time("2026-07-31T00:00:00Z"), "ports": set[80, 443]} — три поля самоочевидны, в потребительском коде по одной строке точного чтения. Суть контраста не в том, что «XLON короче» (может быть и длиннее), а в том, что конвенции переехали из документации в синтаксис — документация забывается и теряется, синтаксис же принудительно исполняется парсером. В этом весь смысл «первоклассных типов».
Типичные ошибки
Симптом: внешние интеграторы сообщают «ваш JSON нелегален» — их парсер падает синтаксической ошибкой на bytes( или set[.
Причина: расширенный синтаксис XLON нелегален для чистых JSON-парсеров — «над» надмножества действует только для XRT и договорившихся сторон.
/* внешний API напрямую возвращает XLON-сериализацию */ str sBody = xrtXlonStringify(pResponse, false, NULL); HttpReply(200, sBody); /* JSON-парсер клиента: syntax error */
/* на внешние каналы только JSON: расширенные типы явно конвертируются на границе */ str sBody = xrtJsonStringify(pResponse, false, NULL); /* bytes/time переводятся в оговорённые JSON-формы до выхода (полевая документация прописывает это) */
Симптом: чтение поля времени даёт отказ или пустоту — «в сериализации же строка time("...")».
Причина: time в XLON — первоклассный тип: в дереве значений его тип Time, а не String; GetString отказывает по семантике точного чтения главы 30 (несовпадение типа).
xstrview When; xrtValueGetString(pUpdated, &When); /* тип Time - точное чтение отказывает */
xtime When; xrtValueGetTime(pUpdated, &When); /* GetTime читает целое число миллисекунд */ /* для показа отформатируйте сами (модуль времени главы 42) */
Практические задания
Разберите документ XLON, содержащий по одному из четырёх расширенных типов; напечатайте имя типа каждого поля через xrtValueType; затем сериализуйте обратно в текст и убедитесь, что пометки типов сохранены как были.
Переведите JSON-конфигурацию (с полем-строкой Base64, полем-строкой ISO-времени и массивом портов) в полно-типовую форму XLON: повышение типов полей (строка → bytes/time, массив → set/intmap), перевод кода разбора на точное чтение, сравнение объёма и читаемости до и после. Подсказка: ценность миграции должна проявиться в «потребительский код стал проще» — пересчитайте, сколько строк преобразования вы удалили.
Реализуйте export(值树, 格式) (дерево значений, формат): XLON выводит полные типы; JSON при выводе понижает расширенные типы по конвенции (bytes → Base64-строка, time → ISO-строка, set → массив, intmap → объект со строковыми ключами), объявляя конвенцию понижения в заголовочном поле. Критерии приёмки: круг XLON экспорт-импорт равен по каждому типу; JSON-экспорт разбирается любым JSON-парсером; таблица соответствия полей двух форматов генерируется автоматически.
Итоги
| Тема | Кратко |
|---|---|
| Отношение надмножества | легальный JSON ⊂ легальный XLON; один парсер и одна строгость |
| Четыре типа | bytes инлайном Base64 / time ISO нормализован в миллисекунды UTC / set с дедупликацией / intmap с целыми ключами |
| Выбор | получатель подконтролен → XLON; неподконтролен → JSON; преобразование на границе, внутри без компромиссов |
| Чтение времени | GetTime читает целое число миллисекунд — time первоклассный тип, не строка |
| Позиционирование ошибок | строка и столбец из того же источника, что у JSON — точность уровня line=1 |
| Формы канала | компакт для машинного канала / красота для просмотра с диска / потоковая запись через обратный вызов |
📖 Справочники API в этой главе:xlon (55 записей)value (159 записей)
Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки