本章导读

JSON 表达不了的东西太多了:二进制要 Base64 手工转、时间戳要字符串约定、集合与整数键映射要退化成数组——每个缺口都是一层自定义约定,每层约定都是互操作的地雷。XLON 是 JSON 的严格超集:合法 JSON 天然是合法 XLON,同时多出四种一等类型——bytes("...")(内联 Base64)、time("...")(ISO 时间戳)、set[...](去重集合)、intmap{...}(整数键映射)。解析器与第 32 章同一套(事件回调按类型字段区分,扩展类型不再退化为字符串),序列化把四种类型原样写回——全类型往返零约定成本。什么时候用 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(第 31 章)本来就有这四种类型,XLON 只是让序列化不再把它们拍扁。超集设计保证平滑:已有的 JSON 工具链照常工作,XLON 文档里的 JSON 部分对任何 JSON 解析器都合法。

33.2概念

四种扩展类型

1bytes("AAEC/w==")二进制内联 Base64——免外部引用,读回即 bytes 值
2time("2026-07-31T0000:00Z"):ISO 8601——解析归一 UTC,事件里给毫秒整数
3set[...]去重集合——语义即第 19 章集合,序列化原样往返
4intmap{ 80"http" }:整数键映射——键不再被迫转字符串

四种类型在值树(第 31 章)里都有对应:Bytes/Time 值、set/intmap 容器——XLON 序列化只是"值树 ↔ 文本"的完整映射,不丢类型信息。事件流(SAX)同样区分:xxlonevent 的 Type 字段直接标出扩展类型,消费方不需要猜"这个字符串是不是其实是个时间"。

严格超集的含义

三条性质值得分开说。正向兼容:任何合法 JSON 文档是合法 XLON——把现有 .json 配置改名为 .xlon 零成本。反向不兼容:带扩展类型的 XLON 文档对纯 JSON 解析器不合法(bytes( 不是 JSON 语法)——XLON 只用于"两端都是 XRT 或约定了 XLON"的通道。互不干扰:解析器同一套,按文档实际内容解析——JSON 文档解析出的值树不含扩展类型,XLON 文档可能含;配置系统不需要为两种格式维护两套代码。

与 JSON 的选型规则

场景选择理由
对外开放 APIJSON通用语,任何客户端可解析
内部配置XLON二进制/时间/集合原生表达,零约定
内部服务间数据XLON两端可控,全类型往返
需要人工编辑的配置JSON 或 XLON时间戳人写 ISO 字符串比毫秒数字友好

规则的核心变量是接收方是否可控:可控 → 用 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"]}

刚才发生了什么。 ① 序列化段:值树里的四种类型原样写出——bytes("AAEC/w==") 带编码声明、time(...) 是 ISO 字符串、set[...] 与 intmap{...} 各用自己的括号语义。类型信息在文本里可见——读文档的人不需要查约定表。② 解析段:bytes = 4——Base64 解码回 4 字节二进制;time = 1785456000000000——ISO 字符串归一成 UTC 毫秒整数(第 3 章 xtime 口径)。③ 最后一行是紧凑序列化(不美化)——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

刚才发生了什么。 ① 校验与读取:XLON 文档的合法性校验与配置字段读取一次走完。② 错误定位:构造缺逗号的输入,line=1 精确报告——第 32 章 JSON 的定位能力原样继承。③ 文件往返:解析 → 读字段 → 写回 → 序列化,闭环验证类型不丢。④ 回调式写出:序列化输出经回调逐块交出——流式落盘/直连 sink 的形态,与第 31 章 Writer 的 sink 一致。⑤ 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 的三个宿主

内部配置(主战场):启动加载 → 值树 → 第 31 章合并——二进制密钥、过期时间、权限集合、端口映射全部一等类型,消费代码零转换。这是 XLON 设计的直接目标场景。内部服务总线:服务间消息序列化——两端都是 XRT,全类型往返省掉两侧的编解码层;与第 21 章队列配合时,值树直接传(免序列化)与 XLON 序列化后传(跨进程)按通道选。对外边界的降级出口:需要对外输出 JSON 时按约定降级(坑 1 的 good 形态)——降级逻辑集中在边界模块,内部值树保持全类型。三个宿主的共同点:类型信息在内部永远一等,只在明确标注的边界降级。

一个对照:同一份配置的两种写法

把"证书配置"用两种格式写一遍,差异立现。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 发给不可控的接收方

症状:外部集成方报告"你的 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 值当字符串读

症状:读时间字段得到失败或空值——"明明序列化里是字符串 time("...")"。

原因:XLON 的 time 是一等类型——值树里的类型是 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 个条目)

点击查看完整签名、参数约束、返回值与错误说明