JSON 教程
JSON(JavaScript Object Notation,JavaScript 对象表示法)是一种轻量级的数据交换格式。它易于阅读和编写,也易于机器解析和生成,是当今 Web 应用最主流的数据传输格式。
为什么 JSON 如此流行
- 轻量:语法简洁,体积小,传输效率高
- 语言无关:几乎所有编程语言都有 JSON 解析库
- 易读:结构清晰,人和机器都能快速理解
- 标准统一:前后端数据交换的"通用语言"
一个 JSON 示例
{
"name": "CodeNest 教程",
"version": "1.0",
"authors": ["张三", "李四"],
"topics": [
{ "id": 1, "title": "JSON 入门", "completed": true },
{ "id": 2, "title": "JSON 进阶", "completed": false }
],
"rating": 4.8
}
JSON 的应用场景
| 场景 | 说明 |
|---|---|
| 前后端数据交互 | AJAX/Fetch 请求返回的数据格式 |
| API 接口 | RESTful API 的标准响应格式 |
| 配置文件 | 如 package.json、tsconfig.json |
| 数据存储 | NoSQL 数据库(MongoDB 等)的文档格式 |
| 数据交换 | 不同系统间的数据传输 |
JSON 与 JavaScript 对象的关系
JSON 源自 JavaScript 的对象字面量,但两者并不完全相同:JSON 的键必须用双引号,且不能有函数、注释等。它们之间通过 JSON.parse() 和 JSON.stringify() 互相转换。
JSON 文件的扩展名是 .json,MIME 类型是 application/json。
JSON 简介
JSON(JavaScript Object Notation)是一种基于文本的、与语言无关的数据交换格式。它由 Douglas Crockford 在 2001 年推广,如今已成为 Web 世界的事实标准。
为什么选择 JSON
| 优点 | 说明 |
|---|---|
| 轻量高效 | 相比 XML,体积更小、解析更快 |
| 易于阅读 | 结构直观,接近自然语言 |
| 跨语言 | Java/Python/JS/Go 等全部支持 |
| 与 JS 天然兼容 | 可直接转为 JavaScript 对象 |
JSON 的诞生历史
在 JSON 出现之前,Web 应用主要用 XML 交换数据。XML 功能强大但过于繁琐,Crockford 从 JavaScript 的对象字面量中获得灵感,抽取出一套极简的文本格式,并迅速被各语言采纳。
JSON 基本结构
JSON 只有两种顶层结构:
- 对象:
{ "键": "值" },无序的键值对集合 - 数组:
[ "值1", "值2" ],有序的值列表
这两种结构可以任意嵌套组合,表达复杂的数据关系。
合法 JSON 的规范
- 键必须用双引号包裹(单引号不合法)
- 字符串值必须用双引号
- 不能有注释、尾随逗号
- 必须是 UTF-8 编码的纯文本
// ❌ 非法:单引号、尾随逗号、注释
{ 'name': '张三', } // 错误
// ✅ 合法
{ "name": "张三" }
在 JavaScript 中直接写 {name: "张三"} 是合法的对象字面量,但它不是合法 JSON —— JSON 要求键必须带双引号。
JSON 语法
JSON 语法是 JSON 数据交换的基础规则。掌握它,你就能读懂和编写任何 JSON 数据。
六种数据类型
| 类型 | 示例 | 说明 |
|---|---|---|
| 字符串 | "hello" | 双引号包裹,支持转义 |
| 数字 | 42 / 3.14 | 整数或浮点数,无引号 |
| 布尔值 | true / false | 小写,无引号 |
| null | null | 表示空值,小写 |
| 数组 | [1, 2, 3] | 有序集合,逗号分隔 |
| 对象 | {"k": "v"} | 键值对集合 |
对象语法
{
"name": "CodeNest",
"year": 2024,
"isOpen": true,
"founder": null
}
对象由 {} 包裹,键值对之间用逗号分隔,最后一对后不能有逗号。
数组语法
["HTML", "CSS", "JavaScript", 42, true, null]
数组由 [] 包裹,元素可以是任意类型,包括对象和其他数组。
嵌套结构
{
"users": [
{ "id": 1, "name": "张三", "skills": ["Python", "AI"] },
{ "id": 2, "name": "李四", "skills": ["Java", "Go"] }
]
}
字符串转义
字符串中的特殊字符需要转义:\" 表示双引号,\\ 表示反斜杠,\n 表示换行,\u4e2d 表示 Unicode 字符(如"中")。
在线工具(如 JSON.cn、jsonlint.com)可以帮你校验 JSON 是否合法,并格式化显示。
JSON vs XML
JSON 和 XML 都用于结构化数据交换,但它们的设计哲学和适用场景有明显差异。
同一份数据的两种表达
<!-- XML 写法 -->
<user>
<name>张三</name>
<age>25</age>
<skills>
<skill>Python</skill>
<skill>AI</skill>
</skills>
</user>
// JSON 写法
{
"name": "张三",
"age": 25,
"skills": ["Python", "AI"]
}
核心差异对比
| 对比项 | JSON | XML |
|---|---|---|
| 体积 | 紧凑、更小 | 标签冗余、更大 |
| 解析速度 | 快 | 较慢 |
| 可读性 | 直观易读 | 标签化、较繁琐 |
| 数据类型 | 原生支持数字/布尔/数组 | 全是文本 |
| 元数据/属性 | 不支持 | 支持属性、命名空间 |
| 注释 | 不支持 | 支持 <!-- --> |
| 验证机制 | JSON Schema | DTD / XSD |
| 扩展性 | 较有限 | 强大(自定义标签) |
什么时候选 JSON
- Web API 数据交换(绝大多数场景)
- 移动端与前端数据
- 追求简单高效的项目
什么时候选 XML
- 文档型数据(如 SVG、RSS、XHTML)
- SOAP Web 服务
- 需要 XPath/XSLT 处理复杂文档
- 金融、医疗等行业标准协议
对于绝大多数 Web 项目,JSON 是更明智的选择;XML 更适合"文档处理"而非"数据传输"。
JSON 对象
JSON 对象是一组无序的键值对集合,用花括号 {} 包裹,是 JSON 中最常用的结构。
对象的基本语法
{
"name": "CodeNest",
"url": "https://www.example.com",
"visits": 1024
}
访问对象属性(JavaScript)
const user = JSON.parse('{"name": "张三", "age": 25}');
// 方式一:点语法
console.log(user.name); // 张三
// 方式二:方括号语法(键名含特殊字符时)
console.log(user["age"]); // 25
// 修改与新增
user.city = "北京";
console.log(user); // {name: "张三", age: 25, city: "北京"}
嵌套对象
{
"user": {
"profile": {
"name": "李四",
"address": { "city": "上海", "zip": "200000" }
}
}
}
// 逐层访问嵌套属性
console.log(data.user.profile.address.city); // 上海
JavaScript 访问对象
const obj = { name: "王五", hobbies: ["读书", "跑步"] };
// 遍历键
for (const key in obj) {
console.log(key + ": " + obj[key]);
}
// 获取所有键 / 值
console.log(Object.keys(obj)); // ["name", "hobbies"]
console.log(Object.values(obj)); // ["王五", ["读书", "跑步"]]
Python 访问 JSON 对象
import json
data = json.loads('{"name": "赵六", "age": 30}')
print(data["name"]) # 赵六
print(data.get("age")) # 30(键不存在返回 None)
JSON 对象的键顺序无关紧要,但同一个对象内不能有重复键(后者会覆盖前者)。
JSON 数组
JSON 数组是有序的值列表,用方括号 [] 包裹,元素之间用逗号分隔。数组中的元素可以是任意 JSON 类型。
数组的基本语法
[ "HTML", "CSS", "JavaScript" ]
混合类型数组
[ 1, "two", true, null, { "k": "v" }, [1, 2] ]
对象数组(最常用)
{
"users": [
{ "id": 1, "name": "张三", "age": 25 },
{ "id": 2, "name": "李四", "age": 30 },
{ "id": 3, "name": "王五", "age": 28 }
]
}
JavaScript 遍历数组
const users = data.users;
// forEach 遍历
users.forEach(user => console.log(user.name));
// map 转换
const names = users.map(user => user.name);
console.log(names); // ["张三", "李四", "王五"]
// filter 过滤
const young = users.filter(user => user.age < 28);
console.log(young); // [张三]
// find 查找
const user = users.find(u => u.id === 2);
console.log(user.name); // 李四
Python 处理 JSON 数组
import json
data = json.loads('[{"id": 1, "name": "张三"}, {"id": 2, "name": "李四"}]')
# 列表推导式
names = [u["name"] for u in data]
print(names) # ['张三', '李四']
# 过滤
adults = [u for u in data if u["id"] > 1]
print(adults)
数组常用操作
| 操作 | JavaScript |
|---|---|
| 长度 | arr.length |
| 追加 | arr.push(4) |
| 移除末尾 | arr.pop() |
| 合并 | arr1.concat(arr2) |
| 排序 | arr.sort() |
API 返回列表数据时,顶层通常就是数组(如 [{...}, {...}]),或放在 {"data": [...]} 中。
JSON.parse()
JSON.parse() 是 JavaScript 内置方法,用于把 JSON 字符串解析为 JavaScript 对象或数组。
基本用法
const jsonStr = '{"name": "张三", "age": 25}';
const obj = JSON.parse(jsonStr);
console.log(obj.name); // 张三
console.log(typeof obj); // object
解析数组
const list = JSON.parse('[1, 2, 3, 4]');
console.log(list.length); // 4
console.log(list[0]); // 1
典型应用:处理 API 响应
fetch("/api/users")
.then(res => res.json()) // 内部就是 JSON.parse
.then(data => {
console.log(data);
});
// 或用 XHR 手动解析
// const data = JSON.parse(xhr.responseText);
错误处理
如果字符串不是合法 JSON,JSON.parse() 会抛出 SyntaxError,必须用 try/catch 捕获:
const badJson = '{"name": "张三",}'; // 尾随逗号,非法
try {
const obj = JSON.parse(badJson);
} catch (err) {
console.error("JSON 解析失败:", err.message);
// 处理:提示用户、使用默认值或重试
}
第二个参数:reviver 函数
可选参数 reviver 可在解析过程中转换每个键值对:
const json = '{"name": "张三", "birth": "1999-01-01"}';
const obj = JSON.parse(json, (key, value) => {
if (key === "birth") {
return new Date(value); // 把日期字符串转成 Date 对象
}
return value;
});
console.log(obj.birth instanceof Date); // true
JSON.parse() 不能解析带单引号或注释的"伪 JSON",如果数据来自不可信来源,务必校验合法性。
JSON.stringify()
JSON.stringify() 与 JSON.parse() 相反,它把 JavaScript 对象/数组转换为 JSON 字符串,用于数据传输或存储。
基本用法
const user = { name: "张三", age: 25, isAdmin: true };
const jsonStr = JSON.stringify(user);
console.log(jsonStr);
// {"name":"张三","age":25,"isAdmin":true}
典型应用:发送数据到服务器
const user = { name: "李四", age: 30 };
fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(user) // 序列化为 JSON 字符串
});
处理嵌套结构
const data = {
title: "CodeNest",
tags: ["教程", "编程"],
meta: { version: "1.0", author: "老王" }
};
console.log(JSON.stringify(data));
// {"title":"CodeNest","tags":["教程","编程"],"meta":{"version":"1.0","author":"老王"}}
第二个参数:replacer 过滤器
可以传入数组(只保留指定键)或函数(自定义处理):
const user = { id: 1, name: "张三", password: "secret123" };
// 只保留指定键(常用于隐藏敏感字段)
console.log(JSON.stringify(user, ["id", "name"]));
// {"id":1,"name":"张三"}
// 函数过滤
console.log(JSON.stringify(user, (key, value) => {
return key === "password" ? undefined : value;
}));
// {"id":1,"name":"张三"}
第三个参数:缩进美化
const data = { a: 1, b: { c: 2 } };
console.log(JSON.stringify(data, null, 2));
// {
// "a": 1,
// "b": {
// "c": 2
// }
// }
// 存储到 localStorage
localStorage.setItem("data", JSON.stringify(data));
注意事项
| 情况 | 结果 |
|---|---|
值为 undefined / 函数 / Symbol | 对象中省略,数组中转为 null |
值为 NaN / Infinity | 转为 null |
| 循环引用 | 抛出 TypeError |
| Date 对象 | 转为 ISO 字符串 |
前后端约定的 JSON 字段命名常使用 camelCase(JS 侧)或 snake_case(Python 侧),可各自转换保持一致。
JSON 使用
综合使用 JSON.parse() 和 JSON.stringify(),就能完成前端与服务器之间完整的数据交换闭环。
完整流程示例
// 1. 把表单数据序列化后发送
const formData = {
username: document.getElementById("username").value,
email: document.getElementById("email").value
};
fetch("/api/register", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(formData)
})
.then(res => res.json()) // 2. 解析响应 JSON
.then(data => {
// 3. 使用返回的数据
if (data.success) {
showMessage("注册成功,欢迎 " + data.user.name);
} else {
showError(data.message);
}
})
.catch(err => console.error("网络错误:", err));
结合 AJAX 的综合案例
function searchBooks(keyword) {
// 构造请求参数
const params = { q: keyword, page: 1, limit: 10 };
fetch("/api/books?" + new URLSearchParams(params))
.then(res => res.json())
.then(data => {
// data: { total: 25, books: [{...}] }
renderBooks(data.books);
updatePagination(data.total);
})
.catch(err => console.error(err));
}
存储到本地
// 保存用户偏好(自动登录)
const prefs = { theme: "dark", lang: "zh-CN", lastVisit: Date.now() };
localStorage.setItem("prefs", JSON.stringify(prefs));
// 读取
const saved = JSON.parse(localStorage.getItem("prefs") || "{}");
console.log(saved.theme); // dark
服务端处理 JSON(Python 示例)
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/api/register", methods=["POST"])
def register():
# 接收 JSON 请求体
data = request.get_json()
username = data.get("username")
email = data.get("email")
# 业务逻辑(存库等)...
# 返回 JSON 响应
return jsonify({"success": True, "user": {"name": username}})
JSON 使用要点
| 环节 | 要点 |
|---|---|
| 发送 | 用 JSON.stringify(),Content-Type 设为 application/json |
| 接收 | 用 res.json() / JSON.parse() |
| 校验 | 解析前检查 res.ok,解析包 try/catch |
| 安全 | 服务端永远校验 JSON 结构,不直接信任 |
API 设计建议统一响应格式,例如 {"code": 0, "message": "ok", "data": {...}},方便前端统一处理。
JSONP 教程
JSONP(JSON with Padding)是一种历史悠久的跨域请求解决方案。在 CORS 出现之前,它是绕过浏览器同源策略的主流手段。
为什么需要 JSONP
浏览器同源策略限制了 AJAX 只能请求同源资源。但 <script> 标签不受此限制,JSONP 正是利用这一点:通过动态创建 <script> 标签加载跨域脚本。
JSONP 工作原理
- 前端定义一个回调函数(如
handleData) - 动态创建
<script>,src 指向跨域接口,附带回调名参数 - 服务器把数据包装成
handleData({...})返回 - 浏览器执行这段"脚本",即调用回调函数
前端实现
function jsonp(url, callbackName, callback) {
// 1. 挂载回调到全局
window[callbackName] = function(data) {
callback(data);
// 清理
script.parentNode.removeChild(script);
delete window[callbackName];
};
// 2. 动态创建 script 标签
const script = document.createElement("script");
script.src = url + "?callback=" + callbackName;
document.body.appendChild(script);
}
// 使用
jsonp("https://api.example.com/user", "handleUser", function(data) {
console.log("跨域获取的数据:", data);
});
服务端响应格式
服务器不返回纯 JSON,而是返回"包裹着 JSON 的 JavaScript 调用":
// 请求:https://api.example.com/user?callback=handleUser
// 响应:
handleUser({"name": "张三", "city": "北京"});
JSONP 的优缺点
| 优点 | 缺点 |
|---|---|
| 实现简单,兼容性极好 | 只支持 GET 请求 |
| 老浏览器也能用 | 无法处理错误(404 等静默失败) |
| 无需服务器特殊配置 | 有安全风险(XSS 注入面) |
| — | 需要服务端配合返回 JS 包装 |
现代替代方案:CORS
如今跨域首选 CORS(跨域资源共享):服务器在响应头加上 Access-Control-Allow-Origin,浏览器即允许跨域读取,且支持任意请求方法、可错误处理、更安全。
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
使用 JSONP 时绝不能信任回调返回的数据(可能被篡改),且回调名要避免与现有全局函数冲突。