Ориентация главы

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概念

Четыре расширенных типа

1bytes("AAEC/w==")двоичные данные инлайном Base64 - без внешних ссылок, читается обратно как значение bytes
2time("2026-07-31T0000:00Z"): ISO 8601 - разбор нормализует в UTC, события дают целое число миллисекунд
3set[...]множество с дедупликацией - семантика множеств главы 19, сериализация проходит круг как есть
4intmap{ 80"http" }: отображение с целыми ключами - ключи больше не обязаны становиться строками

Все четыре типа имеют соответствия в дереве значений (глава 30): значения Bytes/Time, контейнеры set/intmap — сериализация XLON это просто полное отображение «дерево значений ↔ текст» без потери информации о типах. Поток событий (SAX) тоже различает: поле Type у xxlonevent прямо помечает расширенный тип — потребителю не нужно гадать, «не спрятано ли время в этой строке».

Смысл строгого надмножества

Три свойства, каждое стоит проговорить отдельно. Прямая совместимость: любой легальный JSON-документ легален как XLON — переименовать существующие конфигурации .json в .xlon стоит нуля. Обратная несовместимость: документ XLON с расширенными типами нелегален для чистого JSON-парсера (bytes( не JSON-синтаксис) — XLON только для каналов, «где оба конца — XRT или договорились об XLON». Взаимонепричастность: парсер один и тот же, разбор идёт по фактическому содержимому — дерево из JSON-документа не содержит расширенных типов, из XLON-документа может; конфигурационной системе не нужно вести два кодовых пути под два формата.

Правила выбора против JSON

СценарийВыборПричина
Открытый внешний APIJSONлингва франка, любой клиент разберёт
Внутренние конфигурацииXLONдвоичные данные/время/множества в родном виде, ноль конвенций
Данные между внутренними сервисамиXLONоба конца подконтрольны, полный круг типов
Конфигурации для ручного редактированияJSON или XLONISO-строку человеку писать приятнее числа миллисекунд

Центральная переменная правила — подконтролен ли получатель: подконтролен → XLON с полными типами; неподконтролен → JSON ради совместимости. Смешанная форма тоже обычна: поля открытого API — типы JSON, промежуточное представление внутренней обработки — дерево значений (в нём всегда полные типы): преобразование на границе, внутри без компромиссов.

Позиционирование ошибок и строгость

XLON наследует всю строгость главы 31: три шлюза границ ресурсов, отвержение дубликатов ключей, ошибки со строкой и столбцом. Ошибки разбора расширенных типов также локализуются до символа — нелегальный месяц в time("2026-13-01...") указывается прямо в позиции.

33.3示例

Полная программа: круговой проход всех типов

Из образца репозитория examples/data/xlon/main.c — четыре расширенных типа одной записью-чтением:

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;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single impl.c examples/data/xlon/main.c -lws2_32 -liphlpapi{ "blob": bytes("AAEC/w=="), "updated": time("2026-07-31T00:00:00Z"), "roles": set[ ... ], "ports": intmap{ 80: "http", 443: "https" }}bytes = 4time = 1785456000000000{"code":200,"tags":set["xrt"]}

Что только что произошло. (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:

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;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single -include xrt.h impl.c examples/data/xlon_tour/main.c -lws2_32 -liphlpapixlon: valid +/- and read(config) okxlon: error location line=1 okxlon: file roundtrip parse/read -> write/stringify okxlon: sync write via callback okxlon: sink writer full value domain ok

Что только что произошло. (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 короче» (может быть и длиннее), а в том, что конвенции переехали из документации в синтаксис — документация забывается и теряется, синтаксис же принудительно исполняется парсером. В этом весь смысл «первоклассных типов».

Типичные ошибки

Ловушка 1: отправка XLON неподконтрольному получателю:Ловушка 1: отправка 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-формы до выхода (полевая документация прописывает это) */
Ловушка 2: чтение значения time как строки:Ловушка 2: чтение значения time как строки

Симптом: чтение поля времени даёт отказ или пустоту — «в сериализации же строка 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 записей)

Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки