JSON教程:教你读懂并编写常见的JSON数据

JSON教程:教你读懂并编写常见的JSON数据
JSON 是一种轻量的数据交换格式,常用于浏览器与服务器通信、配置文件、日志、接口响应和数据存储。它看起来像 JavaScript 对象,但并不等同于 JavaScript 对象:JSON 有严格的语法,不支持注释、函数、变量或任意表达式。
读懂 JSON,关键是识别它允许的数据类型、分清语法边界,并在程序中正确地解析、校验和生成。本文会从基础语法讲起,提供可运行的 JavaScript 和 Python 示例,并说明数字精度、异常输入与安全使用时需要注意的事项。
一、JSON 适合用来做什么
JSON 的全称是 JavaScript Object Notation,现行规范由 RFC 8259 等标准定义。它是一种文本格式,不绑定具体编程语言。JavaScript、Python、Java、Go、C# 等语言都有 JSON 编解码工具。
常见用途包括:
- HTTP API 数据交换:服务端返回 JSON,客户端解析后展示;客户端也可以通过 POST 请求发送 JSON。
- 配置文件:保存应用名称、开关、端口等结构化设置。具体能否直接使用 JSON,取决于相关工具是否支持。
- 日志和事件:将时间、级别、请求标识等字段组织成结构化记录,便于检索与分析。
- 程序间传递数据:例如命令行工具输出 JSON,供另一个程序读取。
JSON 的优势是文本可读、结构清晰、工具普遍。它也有局限:本身不定义字段含义、数据类型约束、默认值或业务规则;不支持注释;也不是专为大型二进制文件设计的格式。实际项目中,通常还需要额外的接口文档、数据模型或校验规则。
二、JSON 的基本结构
JSON 的顶层值可以是对象、数组、字符串、数字、布尔值或 null。日常接口中最常见的是对象和数组。
1. 对象:一组键值对
对象用花括号包围,键值之间用冒号分隔,多个成员之间用逗号分隔。键必须是双引号包围的字符串:
{
"name": "Lin",
"age": 29,
"active": true
}
这段数据有三个成员。键 name 对应字符串,age 对应数字,active 对应布尔值。键名可以包含空格或非英文字符,但在程序中使用这类键时,往往需要用字符串索引,而不能用点号访问。
2. 数组:有顺序的值列表
数组用方括号包围,元素之间用逗号分隔:
[
"red",
"green",
"blue"
]
数组中的元素可以是不同类型,不过实际项目中通常建议采用一致且有明确含义的结构。例如一组用户记录最好每个元素都使用相同字段。
3. JSON 支持的值类型
| 类型 | 示例 | 说明 |
|---|---|---|
| 对象 | {"id": 7} | 由字符串键和 JSON 值组成 |
| 数组 | [1, 2, 3] | 有顺序的值列表 |
| 字符串 | "hello" | 必须使用双引号 |
| 数字 | 12、-0.5、1e3 | 不加引号;JSON 没有 NaN 和 Infinity |
| 布尔值 | true、false | 必须小写 |
| 空值 | null | 表示 JSON 中的空值 |
对象和数组可以嵌套。例如用户对象包含地址对象和标签数组:
{
"id": 1042,
"name": "周宁",
"address": {
"city": "杭州",
"postcode": "310000"
},
"tags": ["editor", "member"],
"contact": null
}
注意,null 是一个实际的 JSON 值,不等同于“字段不存在”。业务接口如果要区分未提供、明确为空和有具体值,应在接口约定中说明。
三、容易写错的语法边界
字符串必须使用双引号
JSON 字符串和对象键都必须用双引号。单引号是 JavaScript 或 Python 源代码中可能出现的写法,但不是合法 JSON:
{"name": "Lin"}
下面两种写法不是合法 JSON:
{'name': 'Lin'}
{name: "Lin"}
最后一个成员后面不能留逗号
JSON 不允许对象或数组最后一个元素后面有尾随逗号:
{
"name": "Lin",
"age": 29
}
"age": 29 后面不能再加逗号。某些编程语言允许尾随逗号,但严格 JSON 解析器会拒绝它。
JSON 不支持注释
JSON 语法不包含单行或多行注释,以下内容不是合法 JSON:
{
// 用户名称
"name": "Lin"
}
如果需要备注,应使用额外字段、外部文档或采用明确支持注释的配置格式;不要假设所有 JSON 工具都会容忍注释。
不能写变量、函数或表达式
JSON 是数据,不是程序。以下写法都不属于 JSON:
{
"createdAt": new Date(),
"total": 2 + 3,
"callback": function () {}
}
如果希望传递时间,通常使用约定好的字符串格式,例如 ISO 8601 时间字符串;接收方再将它解析成日期对象。JSON 本身没有日期、二进制、函数、未定义值等专用类型。
逗号与冒号不能混用
对象成员内部用冒号连接键和值,成员之间用逗号分隔;数组元素之间用逗号分隔。漏掉逗号、误写冒号,都会导致解析失败。不要把 JSON 当作可以随意排版的文本,遇到错误时最好使用解析器定位,而不是只靠目测。
四、字符串转义与 Unicode
字符串中不能直接出现未转义的双引号、反斜杠或控制字符。JSON 使用反斜杠转义,例如 \" 表示双引号,\\ 表示反斜杠,\n 表示换行。
{
"message": "他说:\"你好\"",
"path": "C:\\work\\notes",
"lines": "第一行\n第二行"
}
JSON 文本通常使用 UTF-8 编码。中文可以直接写入,也可以写成 Unicode 转义形式,例如 "\u4f60\u597d";两种方式解析后表示相同的文本。需要处理国际化数据时,不要自行把转义序列当成额外的字符,也不要在没有明确需求时重复转义。
如果字符串里需要表达一个反斜杠,JSON 文本中必须写成两个反斜杠。尤其是在 Windows 路径、正则表达式或嵌套 JSON 字符串中,转义层数容易混淆。最稳妥的办法是由语言提供的 JSON 序列化器生成文本,而不是手工拼接。
五、读取 JSON:JavaScript 示例
在浏览器或 Node.js 中,可以用 JSON.parse() 将 JSON 文本解析为 JavaScript 值。下面的代码可直接在 Node.js 中运行:
const text = `{
"id": 1042,
"name": "周宁",
"active": true,
"tags": ["editor", "member"]
}`;
const data = JSON.parse(text);
console.log(data.name); // 周宁
console.log(data.tags[0]); // editor
console.log(data.active); // true
JSON.parse() 接收文本并返回对应的对象、数组、字符串、数字、布尔值或 null。如果文本语法不合法,它会抛出异常。处理来自网络、文件或用户输入的数据时,应当捕获异常并提供合适的错误处理:
function parseJson(text) {
try {
return { ok: true, value: JSON.parse(text) };
} catch (error) {
return { ok: false, error: error.message };
}
}
const result = parseJson('{"count": 3}');
if (result.ok) {
console.log(result.value.count);
} else {
console.error("JSON 格式错误:", result.error);
}
解析成功只代表文本符合 JSON 语法,不代表数据符合业务预期。比如 {"count": "3"} 是合法 JSON,但其中的 count 是字符串,不是数字。应用仍需检查字段是否存在、类型是否正确、数值是否在允许范围内。
六、生成 JSON:JavaScript 示例
使用 JSON.stringify() 将 JavaScript 值序列化成 JSON 文本:
const user = {
id: 1042,
name: "周宁",
active: true,
tags: ["editor", "member"],
contact: null
};
const text = JSON.stringify(user);
console.log(text);
需要便于阅读时,可以传入缩进参数:
console.log(JSON.stringify(user, null, 2));
序列化结果是字符串,不是对象。发送 HTTP 请求时,常见做法是将结果放在请求体中,并设置正确的内容类型:
const response = await fetch("/api/users", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(user)
});
if (!response.ok) {
throw new Error(`请求失败:${response.status}`);
}
const result = await response.json();
console.log(result);
这段代码运行在支持 Fetch API 的浏览器或较新的 Node.js 环境中。具体接口地址、请求字段和响应结构应以服务端约定为准。HTTP 中常见的媒体类型是 application/json;仅仅把请求体写成 JSON 文本,并不自动保证服务器会按 JSON 处理,仍需设置合适的请求头并遵循接口规范。
七、Python 中读取和生成 JSON
Python 标准库自带 json 模块,无需安装第三方依赖。下面的代码可保存为 example.py 并用 Python 3 运行:
import json
text = '{"id": 1042, "name": "周宁", "active": true}'
data = json.loads(text)
print(data["name"])
print(data["active"])
output = json.dumps(data, ensure_ascii=False, indent=2)
print(output)
json.loads() 将 JSON 字符串解析成 Python 值,json.dumps() 将 Python 值转换成 JSON 文本。JSON 的 true、false、null 会分别映射为 Python 的 True、False、None。
ensure_ascii=False 让中文以可读字符输出;默认情况下,中文可能被写成 Unicode 转义序列,这仍然是有效 JSON。写入文件时应明确使用 UTF-8:
import json
record = {"name": "周宁", "score": 98}
with open("record.json", "w", encoding="utf-8") as file:
json.dump(record, file, ensure_ascii=False, indent=2)
with open("record.json", "r", encoding="utf-8") as file:
loaded = json.load(file)
print(loaded["score"])
Python 的 json 编码器可以处理常见的字典、列表、字符串、整数、浮点数、布尔值和 None。自定义对象、日期等类型需要先转换成 JSON 支持的值,或显式提供自定义编码方式。不要指望 JSON 自动保留 Python 专有对象类型。
八、数字表示与精度
JSON 只有一种数字语法,不区分整数和浮点数类型,也没有规定所有语言都采用相同的内部表示。不同语言或运行时对数字的精度和范围可能不同。
JavaScript 的普通 Number 使用 IEEE 754 双精度浮点数。整数在 -(2^53 - 1) 到 2^53 - 1 范围内可以安全表示;超出安全整数范围后,部分整数无法精确区分。可以用 Number.isSafeInteger() 检查:
console.log(Number.isSafeInteger(9007199254740991)); // true
console.log(Number.isSafeInteger(9007199254740993)); // false
const value = JSON.parse("9007199254740993");
console.log(value); // 可能已发生精度损失
跨语言传递大整数(如数据库主键、订单号或高精度计数)时,可在接口约定允许的情况下将它们表示为十进制字符串,例如 "9007199254740993"。这样接收方可按需使用大整数类型处理。不要把任意数字都改成字符串:应先确认接口设计和消费者的类型预期。
金额和高精度小数也要谨慎。二进制浮点数不能精确表示所有十进制小数,运算可能出现舍入误差。可根据系统要求使用以最小货币单位计数的整数,或使用支持十进制定点数的类型,并在数据格式中明确单位、精度和舍入规则。
JSON 数字不允许前导零(零本身除外),也不允许 NaN 和正负 Infinity。例如 01、NaN、Infinity 都不是合法 JSON 数字。若业务需要表达“无穷大”或缺失值,应通过接口定义好的字符串、null 或状态字段表示,而不是直接写这些标记。
九、解析成功不等于数据有效
JSON 语法只规定数据如何组织,不规定字段的业务含义。对接口数据至少应检查结构、必填字段、字段类型、取值范围和跨字段关系。例如,用户年龄不仅要是数字,还可能要求是非负整数;邮箱不仅要是字符串,还可能需要符合业务规则。
简单的 JavaScript 校验可以这样写:
function validateUser(value) {
if (value === null || typeof value !== "object" || Array.isArray(value)) {
return "用户数据必须是对象";
}
if (typeof value.name !== "string" || value.name.length === 0) {
return "name 必须是非空字符串";
}
if (!Number.isInteger(value.age) || value.age < 0) {
return "age 必须是非负整数";
}
return null;
}
const user = JSON.parse('{"name":"Lin","age":29}');
const error = validateUser(user);
if (error) {
console.error(error);
} else {
console.log("数据有效");
}
这类手写检查适合演示和少量字段。复杂接口可以采用 JSON Schema 等方案定义结构,并由校验器验证。但 Schema 不能替代业务授权、数据库约束和服务端安全检查;校验规则本身也需要维护和测试。
十、常见错误与排查方法
1. 把对象文本当成 JavaScript 对象
{name: "Lin"} 在某些 JavaScript 代码上下文中可能是对象字面量,但不是 JSON。作为 JSON 文本时,键必须加双引号,并使用 JSON.parse() 解析。
2. 混淆序列化和解析
JSON.parse() 的输入是 JSON 文本,输出是语言中的值;JSON.stringify() 的输入是语言中的值,输出是文本。不要对一个已经是对象的值重复解析,也不要把序列化出的字符串误当成对象直接访问属性。
3. 误以为所有字段都一定存在
服务端响应字段可能可选,也可能因版本或权限而变化。访问前应检查字段及其类型;不能仅凭一次成功响应推断所有数据都符合相同结构。
4. 用正则表达式“验证 JSON”
JSON 可以任意嵌套,单一简单正则表达式难以可靠处理完整语法。应使用语言自带解析器;若还要验证字段规则,再执行结构校验。
5. 不区分无效 JSON 和无效业务数据
解析器报错通常意味着语法不合法;解析成功但字段不符合约定,则是数据校验失败。把两类错误分开记录,更容易定位是传输、格式还是业务问题。
6. 多次拼接或嵌套转义
手工拼字符串时,用户输入中的引号、反斜杠和换行容易破坏 JSON。应先构造普通对象,再调用序列化器;不要通过字符串连接来生成包含不可信输入的 JSON 文本。
十一、安全使用时要注意什么
解析 JSON 不等于执行代码。与 eval() 不同,标准 JSON 解析器按数据格式解析文本,不会把其中的函数或语句当作可执行代码。因此,不要为了“解析 JSON”使用 eval():这会扩大攻击面,也容易造成代码注入。
不过,JSON 格式本身不是安全边界。对不可信数据,应采取以下措施:
- 限制输入规模和处理成本:大型文本、深层嵌套或极多数组元素可能消耗大量内存和 CPU。JSON 规范没有规定适用于所有实现的统一最大文本长度或嵌套深度,具体限制取决于解析器、运行环境和应用。应按业务设定请求体、文件大小和处理时间上限,并了解所用库的限制。
- 校验数据并执行授权:格式正确不代表用户有权执行相应操作。服务端仍需进行身份认证、权限判断、字段校验和业务规则检查。
- 避免危险的对象合并:某些程序把解析得到的对象递归合并到配置对象或原型对象中,可能引发原型污染等问题。风险往往出现在不安全的后续处理,而不是“JSON 会执行代码”。处理外部键时应使用安全的合并逻辑、限制允许字段,并遵循运行环境和库的安全建议。
- 输出到网页时做上下文转义:JSON 字符串若被插入 HTML、属性或脚本上下文,必须按目标上下文进行恰当编码。JSON 编码本身不等同于 HTML 安全转义,也不应把不可信内容直接拼进 HTML。
- 保护敏感信息:不要因为内容是 JSON 就把密码、令牌或个人数据写入公开日志。传输和存储保护应按系统的安全要求设计。
不同语言的解析器可能对重复键采取不同处理方式。RFC 8259 建议对象成员名称唯一;若文本中同一个键重复出现,接收方的行为可能不一致。因此,生成 JSON 时应避免重复键;接收外部数据时,若重复键会影响安全或业务判断,应使用能检测重复键的处理方式,不能假定所有解析器都会报错。
十二、实际项目中的建议流程
- 先明确数据结构:哪些字段必填,字段类型、格式、单位和可选值是什么。
- 由程序对象生成 JSON,尽量不要手工拼接文本。
- 接收数据后先解析,再校验结构和业务规则。
- 大整数、金额、日期、二进制和缺失值等特殊数据,提前制定跨语言约定。
- 对外部输入设定大小等资源限制,并避免用不安全的对象合并逻辑。
- 将 JSON 放入 HTTP 请求或响应时,按接口约定设置内容类型和字符编码,并处理错误状态。
结语
JSON 的核心并不复杂:对象用键值对组织数据,数组保存有序列表,值由字符串、数字、布尔值、null、对象和数组构成。真正容易出问题的地方,是把 JSON 与某种编程语言的对象语法混为一谈、忽视数字精度、只解析不校验,或把格式正确误认为数据可信。
日常使用时,记住三条原则:遵守严格语法;用标准解析器和序列化器;对外部数据做结构校验、资源限制和权限检查。掌握这几条,就能在接口、配置和日志等常见场景中更可靠地读写 JSON。









