JSON 语法与数据模型

本节目标

学完后,你能把“文本长得像 JSON”和“它是合法 JSON”分开;能识别六类值、对象成员和数组元素;能解释顶层标量、合法空白、转义,以及缺失、null 和空值为什么不是一回事。

先分清三层对象

初学时最容易把下面三样混在一起:

python_value = {"enabled": True, "fallback": None}
json_text = '{"enabled": true, "fallback": null}'
json_string_value = '"{\\"enabled\\": true}"'
  • python_value 是内存中的 Python dict;
  • json_text 是 Python 字符串,字符串内容恰好是一份 JSON 文本;
  • json_string_value 解析后得到的仍是字符串,其内容才像一个对象。这通常是误做了两次序列化。

接口传输的是字节;解析器把字节解码为文本,再把文本转换成语言对象。后续 Schema 验证和业务判断处理的是解析后的值,不是大括号本身。

一个 JSON 文本表示一个值

RFC 8259 的核心形式可以读作:合法空白 + 一个值 + 合法空白。顶层值不必是对象或数组,以下都是合法的单个 JSON 文本:

{"name":"agent"}
["search", "read"]
42
null

某些 API 另行要求顶层必须是对象。这是接口契约,不是 JSON 通用语法。收到数据后应先解析,再显式检查顶层类型。

六类值与 Python 映射

{
  "name": "meeting-assistant",
  "enabled": true,
  "max_steps": 8,
  "temperature": 0.2,
  "tools": ["search_notes", "read_calendar"],
  "fallback": null,
  "limits": {
    "timeout_seconds": 30
  }
}
JSON 类别例子Python 默认结果关键边界
object{"a": 1}dict成员名必须是字符串;语义上不依赖顺序。
array[1, "x", null]list元素有序且可以异构。
string"agent"str使用双引号;控制字符需转义。
number8、0.2、1e3int、floatJSON 不声明位宽、Decimal 或日期类型。
booleantrue、falseTrue、FalseJSON 使用小写字面量。
nullnullNone表示显式空值;具体业务含义由契约定义。

对象成员写成 "name": value,成员之间用逗号;数组元素之间也用逗号。最后一项后不能保留尾逗号。

合法空白非常有限

JSON 语法只把四种字符当作结构外空白:空格、Tab、LF 和 CR。全角空格、不可见的任意 Unicode 空白或复制来的特殊字符不一定合法。为了调试,先查看解析错误的行列号和原始字节来源,不要通过“删除所有看不见字符”掩盖数据问题。

最常见的非法写法

下面每一行都不是标准 JSON:

{'name': 'agent'}           # 单引号
{name: "agent"}            # 键没有双引号
{"enabled": True}          # Python 布尔值
{"fallback": None}         # Python 空值
{"items": [1, 2,]}         # 尾逗号
{"value": undefined}       # JSON 没有 undefined
{"id": 001}                # 整数有无意义前导零
{"score": NaN}             # RFC 8259 不允许非有限数字
{"hex": 0x10}              # 不支持十六进制数字
{"a": 1} {"b": 2}         # 两个值没有 framing

示例后的 # 是教学说明,不属于被判断的文本。标准 JSON 本身不支持注释。JSON5 等扩展格式可以允许其中一些写法,但不能把扩展语法悄悄送给严格 JSON API。

字符串、转义与 Windows 路径

字符串中的双引号、反斜杠和 U+0000–U+001F 控制字符必须转义:

{
  "quote": "他说:\"停止任务\"",
  "path": "D:\\data\\input.json",
  "two_lines": "first\nsecond",
  "tab": "left\tright",
  "chinese": "中文可直接使用 UTF-8"
}

不要把 JSON 转义和 Python 源码转义混为一层。在 Python 源码里手写同一文本时,反斜杠可能还要再经过 Python 字符串解释。更可靠的方式是先构造 Python 对象,再让 json.dumps 编码。

缺失、null、空字符串与空容器

{}
{"deadline": null}
{"deadline": ""}
{"reviewers": []}

它们分别可能表示:

  • 字段缺失:未提供、沿用旧值或使用默认值;
  • null:显式清除或未知;
  • 空字符串:提供了字符串,但没有字符;
  • 空数组:提供了一个集合,而且集合当前没有元素。

解析器只保留数据形状,不知道“清除”和“沿用”哪一个才是业务意图。契约必须为每个字段写清楚。

文本相同、值相同与业务相同

下面两段文本字节不同,但解析后的对象可以表示相同的名值映射:

{"a":1,"b":2}
{
  "b": 2,
  "a": 1
}

对象成员顺序不应成为跨系统业务语义;数组顺序则有语义。字符串 "1" 与数字 1 不相等,null 与字段缺失也不相等。若要做签名、哈希或缓存键,不能直接假定任意序列化文本稳定,后续会学习 canonicalization。

常见错误与排查

  • 把 Python repr 复制为 JSON:先用 json.dumps 生成,再用解析器验证。
  • 手工拼接 JSON:引号、反斜杠和控制字符容易破坏边界;构造对象后序列化。
  • 看到 JSONDecodeError 就修改 Schema:语法解析发生在 Schema 之前,先修文本。
  • 假定所有 JSON 都是对象:顶层可以是标量,但应用可以显式拒绝。
  • 把注释写进生产 payload:改用独立文档字段或选择明确支持注释的配置格式。

练习

  1. 把 Python 字面量 {'ok': True, 'value': None} 改成合法 JSON。
  2. 为路径 D:\agent\config.json 和包含双引号的句子写合法 JSON 字符串。
  3. 判断 42、[true]、{"x": 1,}、{"x": +1} 是否合法,并说明理由。
  4. 为 PATCH 请求中的 display_name 定义“缺失、null、空字符串”三种语义。
  5. 解释为什么对象字段换序通常不应导致业务变化,而数组元素换序可能会。

自测

  1. JSON 对象键能否是未加引号的数字?
  2. 顶层 false 是否是合法 JSON 文本?
  3. JSON 是否原生支持日期、注释和 undefined?
  4. 为什么 JSON 字符串中的 Windows 路径要写双反斜杠?
  5. 文本不同是否必然表示解析后的值不同?

小结与下一步

JSON 是“一个序列化值”的语法,不是权限系统、数据库或业务模型。下一节把文本交给 Python,并观察标准库为兼容性保留的默认行为:Python 解析、序列化与严格模式。返回 JSON 学习目录。

参考资料

资料复核日期:2026-07-14。