Python JSON 练习题与笔试面试题(含答案)
第一部分:基础练习题
1. 认识 JSON 格式
题目: 判断下面哪些是合法的 JSON,哪些不合法,并说明原因:
① {"name": 'Zhang San', "age": 25}
② {"name": "张三", "age": 25}
③ {name: "张三", age: 25}
④ {"scores": [90, 85, 92], "pass": true, "note": null}
⑤ {"price": 9.99, "discount": undefined}
⑥ [1, 2, 3, 4, 5]
⑦ {"data": {"nested": {"deep": "value"}}}
⑧ {'key': 'value',}
参考答案:
① ❌ 不合法:JSON 字符串只能用双引号,不能用单引号
② ✅ 合法:标准 JSON 对象
③ ❌ 不合法:键名必须加双引号,不能裸写
④ ✅ 合法:包含数组、布尔值、null,都是合法的 JSON 类型
⑤ ❌ 不合法:JSON 没有 undefined 这个值(这是 JavaScript 特有的)
⑥ ✅ 合法:JSON 顶层可以是数组
⑦ ✅ 合法:允许任意深度的嵌套
⑧ ❌ 不合法:① 用了单引号;② 最后有尾随逗号(trailing comma),JSON 不允许
2. dumps 基础转换
题目: 用 json.dumps() 把下面的 Python 对象转成 JSON 字符串,要求:
- 中文直接显示(不转义)
- 4 个空格缩进
- 键名按字母排序
student = {
"姓名": "李明",
"年龄": 18,
"成绩": 95.5,
"是否在校": True,
"备注": None,
"科目": ["数学", "语文", "英语"],
}
参考答案:
import json
student = {
"姓名": "李明",
"年龄": 18,
"成绩": 95.5,
"是否在校": True,
"备注": None,
"科目": ["数学", "语文", "英语"],
}
result = json.dumps(
student,
ensure_ascii=False, # 中文直接显示
indent=4, # 4空格缩进
sort_keys=True, # 键名排序
)
print(result)
输出:
{
"备注": null,
"姓名": "李明",
"年龄": 18,
"是否在校": true,
"成绩": 95.5,
"科目": [
"数学",
"语文",
"英语"
]
}
关键点:
True→true(小写),None→null,这是 Python 和 JSON 的重要区别sort_keys=True按键名的字典序排序(汉字按 Unicode 码点)
3. loads 基础解析
题目: 解析下面的 JSON 字符串,然后:
- 打印所有学生的姓名
- 计算平均分
- 找出成绩最高的学生
json_str = '''
[
{"name": "张三", "score": 85, "city": "北京"},
{"name": "李四", "score": 92, "city": "上海"},
{"name": "王五", "score": 78, "city": "北京"},
{"name": "赵六", "score": 96, "city": "广州"},
{"name": "钱七", "score": 88, "city": "上海"}
]
'''
参考答案:
import json
json_str = '''
[
{"name": "张三", "score": 85, "city": "北京"},
{"name": "李四", "score": 92, "city": "上海"},
{"name": "王五", "score": 78, "city": "北京"},
{"name": "赵六", "score": 96, "city": "广州"},
{"name": "钱七", "score": 88, "city": "上海"}
]
'''
students = json.loads(json_str)
# 1. 打印所有姓名
print("所有学生:", [s["name"] for s in students])
# 2. 计算平均分
avg = sum(s["score"] for s in students) / len(students)
print(f"平均分:{avg:.1f}")
# 3. 最高分
best = max(students, key=lambda s: s["score"])
print(f"最高分:{best['name']},{best['score']}分")
输出:
所有学生: ['张三', '李四', '王五', '赵六', '钱七']
平均分:87.8
最高分:赵六,96分
4. dump 和 load——读写文件
题目: 编写一个简单的”待办事项”程序:
add_todo(text):添加一条待办,保存到todos.jsoncomplete_todo(index):把第 index 条标记为已完成show_todos():显示所有待办(未完成的用[ ],已完成用[✓])
参考答案:
import json
import os
TODO_FILE = "todos.json"
def load_todos():
if os.path.exists(TODO_FILE):
with open(TODO_FILE, "r", encoding="utf-8") as f:
return json.load(f)
return []
def save_todos(todos):
with open(TODO_FILE, "w", encoding="utf-8") as f:
json.dump(todos, f, ensure_ascii=False, indent=2)
def add_todo(text):
todos = load_todos()
todos.append({"text": text, "done": False})
save_todos(todos)
print(f"已添加:{text}")
def complete_todo(index):
todos = load_todos()
if 0 <= index < len(todos):
todos[index]["done"] = True
save_todos(todos)
print(f"已完成:{todos[index]['text']}")
else:
print(f"无效编号:{index}(共 {len(todos)} 条)")
def show_todos():
todos = load_todos()
if not todos:
print("暂无待办事项")
return
print(f"待办列表(共 {len(todos)} 条):")
for i, todo in enumerate(todos):
mark = "✓" if todo["done"] else " "
print(f" {i}. [{mark}] {todo['text']}")
# 测试
add_todo("学习 Python JSON 模块")
add_todo("完成作业")
add_todo("锻炼身体")
add_todo("读一本书")
show_todos()
complete_todo(0)
complete_todo(2)
print()
show_todos()
输出:
已添加:学习 Python JSON 模块
已添加:完成作业
已添加:锻炼身体
已添加:读一本书
待办列表(共 4 条):
0. [ ] 学习 Python JSON 模块
1. [ ] 完成作业
2. [ ] 锻炼身体
3. [ ] 读一本书
已完成:学习 Python JSON 模块
已完成:锻炼身体
待办列表(共 4 条):
0. [✓] 学习 Python JSON 模块
1. [ ] 完成作业
2. [✓] 锻炼身体
3. [ ] 读一本书
5. 处理缺失键和默认值
题目: 解析以下 JSON,每个用户的字段不一定齐全,要求用 .get() 安全取值,输出标准格式(缺失字段用默认值填充):
json_str = '''
[
{"id": 1, "name": "Alice", "age": 28, "email": "alice@test.com", "vip": true},
{"id": 2, "name": "Bob", "age": 35},
{"id": 3, "name": "Carol", "email": "carol@test.com"},
{"id": 4}
]
'''
参考答案:
import json
json_str = '''
[
{"id": 1, "name": "Alice", "age": 28, "email": "alice@test.com", "vip": true},
{"id": 2, "name": "Bob", "age": 35},
{"id": 3, "name": "Carol", "email": "carol@test.com"},
{"id": 4}
]
'''
users = json.loads(json_str)
for user in users:
uid = user.get("id", "N/A")
name = user.get("name", "未知用户")
age = user.get("age", "未填写")
email = user.get("email", "未填写")
vip = "⭐VIP" if user.get("vip", False) else "普通"
print(f"[{uid}] {name:8s} 年龄:{str(age):5s} 邮箱:{email:25s} {vip}")
输出:
[1] Alice 年龄:28 邮箱:alice@test.com ⭐VIP
[2] Bob 年龄:35 邮箱:未填写 普通
[3] Carol 年龄:未填写 邮箱:carol@test.com 普通
[4] 未知用户 年龄:未填写 邮箱:未填写 普通
6. 找错题:常见错误修复
题目: 下面的代码有 4 处错误,找出并修复:
import json
# 错误1
data = {'name': '张三', 'age': 25}
json_str = json.dumps(data) # 中文会被转义,但这里数据是英文,先忽略
# 错误2:想解析 JSON 字符串
response = '{"code": 200, "msg": "success"}'
result = json.load(response) # 用了 load 而不是 loads!
print(result["msg"])
# 错误3:想把数据写入文件
save_data = {"key": "值"}
with open("out.json", "w") as f:
json.dump(save_data, f) # 中文可能乱码,缺少编码参数
# 错误4:想解析一个可能不合法的 JSON
user_input = "{'invalid': 'json'}" # 单引号,不合法
data2 = json.loads(user_input) # 未做异常处理,直接崩溃
print(data2)
参考答案(含错误说明):
import json
# 修复1:不涉及中文时可以不加,但养成习惯加上
data = {"name": "张三", "age": 25}
json_str = json.dumps(data, ensure_ascii=False) # 修复:中文加 ensure_ascii=False
# 修复2:字符串用 loads,文件用 load
response = '{"code": 200, "msg": "success"}'
result = json.loads(response) # 修复:load → loads
print(result["msg"]) # success
# 修复3:写文件时指定编码
save_data = {"key": "值"}
with open("out.json", "w", encoding="utf-8") as f: # 修复:加 encoding="utf-8"
json.dump(save_data, f, ensure_ascii=False) # 修复:加 ensure_ascii=False
# 修复4:用 try/except 捕获解析错误
user_input = "{'invalid': 'json'}"
try:
data2 = json.loads(user_input)
print(data2)
except json.JSONDecodeError as e:
print(f"JSON 格式不合法:{e}") # 修复:加异常处理
错误总结:
| 错误 | 说明 | 修复 |
|---|---|---|
json.load(字符串) |
load 用于文件对象,字符串要用 loads |
改为 json.loads() |
写文件没有 encoding |
Windows 默认 gbk,中文会乱码 | 加 encoding="utf-8" |
没有 ensure_ascii=False |
中文变成 uXXXX 转义 |
加 ensure_ascii=False |
| 没有异常处理 | 不合法的 JSON 直接让程序崩溃 | 用 try/except json.JSONDecodeError |
7. 自定义编码——处理 datetime
题目: 有如下包含 datetime 的数据,直接 json.dumps() 会报错。用两种方式解决:
default参数(函数方式)- 自定义
JSONEncoder类
from datetime import datetime, date
order = {
"order_id": "ORD-2024-001",
"created_at": datetime(2024, 1, 15, 10, 30, 0),
"delivery_date": date(2024, 1, 20),
"amount": 299.00,
"items": ["商品A", "商品B"],
}
参考答案:
import json
from datetime import datetime, date
order = {
"order_id": "ORD-2024-001",
"created_at": datetime(2024, 1, 15, 10, 30, 0),
"delivery_date": date(2024, 1, 20),
"amount": 299.00,
"items": ["商品A", "商品B"],
}
# ─── 方式一:default 参数 ───
def datetime_handler(obj):
if isinstance(obj, datetime):
return obj.strftime("%Y-%m-%d %H:%M:%S")
if isinstance(obj, date):
return obj.strftime("%Y-%m-%d")
raise TypeError(f"无法序列化类型:{type(obj)}")
result1 = json.dumps(order, default=datetime_handler, ensure_ascii=False, indent=2)
print("方式一(default函数):")
print(result1)
print()
# ─── 方式二:自定义 JSONEncoder 类 ───
class DateTimeEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return obj.strftime("%Y-%m-%d %H:%M:%S")
if isinstance(obj, date):
return obj.strftime("%Y-%m-%d")
return super().default(obj)
result2 = json.dumps(order, cls=DateTimeEncoder, ensure_ascii=False, indent=2)
print("方式二(自定义类):")
print(result2)
输出:
方式一(default函数):
{
"order_id": "ORD-2024-001",
"created_at": "2024-01-15 10:30:00",
"delivery_date": "2024-01-20",
"amount": 299.0,
"items": ["商品A", "商品B"]
}
8. 综合题:学生成绩管理系统
题目: 实现一个基于 JSON 文件的学生成绩管理系统,支持:
add_student(name, scores):添加学生(scores 是科目→分数的字典)get_student(name):查询单个学生信息get_ranking():按总分降序列出所有学生get_subject_top(subject):查询某科目最高分的学生
参考答案:
import json
import os
DB_FILE = "grade_db.json"
def _load():
if os.path.exists(DB_FILE):
with open(DB_FILE, "r", encoding="utf-8") as f:
return json.load(f)
return {}
def _save(data):
with open(DB_FILE, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
def add_student(name, scores):
"""添加或更新学生成绩"""
db = _load()
db[name] = {
"scores": scores,
"total": sum(scores.values()),
"average": sum(scores.values()) / len(scores),
}
_save(db)
print(f"已保存 {name} 的成绩,总分:{db[name]['total']}")
def get_student(name):
"""查询单个学生"""
db = _load()
if name not in db:
print(f"未找到学生:{name}")
return None
info = db[name]
print(f"n学生:{name}")
print(f" 总分:{info['total']},平均分:{info['average']:.1f}")
for subject, score in info["scores"].items():
print(f" {subject}:{score}")
return info
def get_ranking():
"""按总分降序排名"""
db = _load()
if not db:
print("暂无数据")
return
ranked = sorted(db.items(), key=lambda x: x[1]["total"], reverse=True)
print("n=== 总分排名 ===")
for rank, (name, info) in enumerate(ranked, 1):
print(f" 第{rank}名 {name:6s} 总分:{info['total']} 均分:{info['average']:.1f}")
def get_subject_top(subject):
"""查询某科目最高分"""
db = _load()
best_name, best_score = None, -1
for name, info in db.items():
score = info["scores"].get(subject, 0)
if score > best_score:
best_name, best_score = name, score
if best_name:
print(f"n{subject} 最高分:{best_name} {best_score}分")
else:
print(f"没有找到 {subject} 科目的数据")
# 测试
add_student("张三", {"数学": 90, "语文": 85, "英语": 92})
add_student("李四", {"数学": 78, "语文": 95, "英语": 88})
add_student("王五", {"数学": 95, "语文": 80, "英语": 76})
add_student("赵六", {"数学": 88, "语文": 92, "英语": 94})
get_student("李四")
get_ranking()
get_subject_top("数学")
get_subject_top("英语")
输出:
已保存 张三 的成绩,总分:267
已保存 李四 的成绩,总分:261
已保存 王五 的成绩,总分:251
已保存 赵六 的成绩,总分:274
学生:李四
总分:261,平均分:87.0
数学:78
语文:95
英语:88
=== 总分排名 ===
第1名 赵六 总分:274 均分:91.3
第2名 张三 总分:267 均分:89.0
第3名 李四 总分:261 均分:87.0
第4名 王五 总分:251 均分:83.7
数学 最高分:王五 95分
英语 最高分:赵六 94分
第二部分:笔试面试题
面试题 1:dumps 和 dump 的区别是什么?
标准答案:
区别在于操作对象不同:
json.dumps(obj) → 把 Python 对象序列化为 JSON 字符串(内存操作)
json.dump(obj, file) → 把 Python 对象序列化,直接写入文件对象(文件操作)
json.loads(s) → 把 JSON 字符串反序列化为 Python 对象
json.load(file) → 从文件对象读取 JSON,反序列化为 Python 对象
记忆口诀:
有 s(string)→ 操作字符串
没有 s → 操作文件
dump(obj, f) 本质上等价于:
f.write(json.dumps(obj))
import json
data = {"name": "张三"}
# dumps:返回字符串
s = json.dumps(data, ensure_ascii=False)
print(type(s)) # <class 'str'>
# dump:写入文件(无返回值)
with open("a.json", "w", encoding="utf-8") as f:
ret = json.dump(data, f, ensure_ascii=False)
print(ret) # None(无返回值)
面试题 2:Python 数据和 JSON 数据类型是怎么对应的?
标准答案:
| Python | JSON | 特殊说明 |
|---|---|---|
dict |
object {} |
字典的键会被强制转成字符串 |
list |
array [] |
|
tuple |
array [] |
反序列化后变成 list,类型信息丢失 |
str |
string | |
int |
number | |
float |
number | nan/inf 默认生成非标准 JSON |
True |
true |
|
False |
false |
|
None |
null |
|
set、bytes、datetime |
❌ 无法直接序列化 | 需要 default 自定义处理 |
import json
# 验证关键转换
data = {"tuple": (1, 2, 3), "bool": True, "none": None}
s = json.dumps(data)
back = json.loads(s)
print(type(back["tuple"])) # list(不是 tuple!)
print(back["bool"]) # True(Python bool)
print(back["none"]) # None(Python None)
print(s)
# {"tuple": [1, 2, 3], "bool": true, "none": null}
面试题 3:如何处理 JSON 中的中文乱码问题?
标准答案:
中文乱码的根源有两处:
1. dumps/dump 时:默认 ensure_ascii=True,中文被转义成 uXXXX
解决:加 ensure_ascii=False 参数
2. dump 写文件时:open() 没有指定编码,Windows 默认用 gbk
解决:open(path, "w", encoding="utf-8")
完整正确写法:
import json
data = {"message": "你好,世界!"}
# ─── 字符串操作 ───
# 错误:中文被转义
print(json.dumps(data))
# {"message": "u4f60u597duff0cu4e16u754cuff01"}
# 正确:中文直接显示
print(json.dumps(data, ensure_ascii=False))
# {"message": "你好,世界!"}
# ─── 文件操作 ───
# 错误写法(Windows 上中文乱码):
# with open("a.json", "w") as f:
# json.dump(data, f)
# 正确写法(始终指定 utf-8):
with open("a.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False)
# 读取时同样要指定编码:
with open("a.json", "r", encoding="utf-8") as f:
result = json.load(f)
print(result["message"]) # 你好,世界!
面试题 4:json.loads() 失败时会抛出什么异常?如何处理?
标准答案:
json.loads() 解析失败时抛出:json.JSONDecodeError
它是 ValueError 的子类,所以 except ValueError 也能捕获。
JSONDecodeError 包含以下有用属性:
e.msg 错误描述
e.doc 原始字符串
e.pos 出错的字符位置(整数)
e.lineno 出错的行号
e.colno 出错的列号
import json
def safe_parse(json_str, default=None):
"""安全解析 JSON,失败返回默认值"""
try:
return json.loads(json_str)
except json.JSONDecodeError as e:
print(f"解析失败:{e.msg}(第{e.lineno}行,第{e.colno}列)")
return default
# 测试
print(safe_parse('{"name": "张三"}')) # {'name': '张三'}
print(safe_parse("{'name': '张三'}", {})) # 解析失败:... → {}
print(safe_parse("", [])) # 解析失败:... → []
面试题 5:如何序列化 datetime 对象?列举至少两种方法。
标准答案:
import json
from datetime import datetime
data = {"name": "张三", "created_at": datetime(2024, 1, 15, 10, 30)}
# ─── 方法1:default 参数(最简洁)───
result1 = json.dumps(
data,
default=lambda o: o.isoformat() if isinstance(o, datetime) else str(o),
ensure_ascii=False
)
print(result1)
# {"name": "张三", "created_at": "2024-01-15T10:30:00"}
# ─── 方法2:自定义 JSONEncoder 类(最规范)───
class DateTimeEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return obj.strftime("%Y-%m-%d %H:%M:%S")
return super().default(obj)
result2 = json.dumps(data, cls=DateTimeEncoder, ensure_ascii=False)
print(result2)
# {"name": "张三", "created_at": "2024-01-15 10:30:00"}
# ─── 方法3:序列化前手动转换 ───
data_copy = dict(data)
data_copy["created_at"] = data_copy["created_at"].strftime("%Y-%m-%d %H:%M:%S")
result3 = json.dumps(data_copy, ensure_ascii=False)
print(result3)
# {"name": "张三", "created_at": "2024-01-15 10:30:00"}
面试题 6:以下代码的输出是什么?(类型转换考查)
题目:
import json
d1 = {1: "one", 2: "two"}
s1 = json.dumps(d1)
d2 = json.loads(s1)
print(s1)
print(d2[1]) # ① 会报错还是输出 "one"?
print(d2["1"]) # ② 会报错还是输出 "one"?
print(type(list(d2.keys())[0])) # ③ 键的类型是什么?
参考答案:
import json
d1 = {1: "one", 2: "two"}
s1 = json.dumps(d1)
d2 = json.loads(s1)
print(s1)
# {"1": "one", "2": "two"} ← 整数键被强制转成了字符串!
print(d2[1]) # ① KeyError: 1 → 会报错!键已变成字符串"1"
print(d2["1"]) # ② "one" → 正确,键是字符串"1"
print(type(list(d2.keys())[0])) # ③ <class 'str'>
考点: JSON 规范要求所有键必须是字符串,所以 Python 字典的整数键在序列化后变成字符串,反序列化后回来的键也是字符串,不再是整数。这是一个常见的坑。
面试题 7:以下代码的输出是什么?(类型还原考查)
题目:
import json
original = {
"data": (1, 2, 3),
"flag": True,
"value": None,
}
s = json.dumps(original)
back = json.loads(s)
print(type(original["data"])) # ①
print(type(back["data"])) # ②
print(back["flag"]) # ③
print(back["value"]) # ④
print(original == back) # ⑤
参考答案:
# ① <class 'tuple'> 原始是元组
# ② <class 'list'> JSON 数组反序列化后变成 list!
# ③ True JSON true → Python True
# ④ None JSON null → Python None
# ⑤ False 因为 (1,2,3) != [1,2,3],两者不相等
考点: 元组(tuple)序列化为 JSON 数组,反序列化后变成列表(list),类型信息丢失,这是 JSON 的固有局限。
面试题 8:如何用 json 模块深拷贝一个对象?有什么局限?
标准答案:
import json
import copy
data = {
"name": "张三",
"scores": [90, 85, 92],
"info": {"city": "北京"},
}
# ─── 方法:序列化再反序列化 = 深拷贝 ───
deep_copy = json.loads(json.dumps(data, ensure_ascii=False))
# 验证是深拷贝
deep_copy["scores"].append(100)
deep_copy["info"]["city"] = "上海"
print(data["scores"]) # [90, 85, 92](未被修改)
print(data["info"]["city"]) # 北京(未被修改)
# ─── 局限性 ───
# 1. 只支持 JSON 可序列化的类型(不支持 datetime、set、bytes 等)
# 2. 元组会变成列表
# 3. 整数键会变成字符串键
# 4. 性能比 copy.deepcopy() 慢(需要序列化和反序列化两次)
# 对比:copy.deepcopy 更通用
from datetime import datetime
data2 = {"time": datetime.now()}
deep2 = copy.deepcopy(data2) # ✅ 支持 datetime
# json.loads(json.dumps(data2)) # ❌ 报错:datetime 无法序列化
面试题 9:indent 参数传字符串(制表符)和传整数有什么区别?
标准答案:
import json
data = {"name": "张三", "age": 25}
# indent 传整数:N 个空格缩进
print(json.dumps(data, ensure_ascii=False, indent=4))
# {
# "name": "张三",
# "age": 25
# }
# indent 传字符串:用该字符串作为缩进符
print(json.dumps(data, ensure_ascii=False, indent="t"))
# {
# "name": "张三",
# "age": 25
# }
# indent 传两个空格(常见于 JavaScript 社区)
print(json.dumps(data, ensure_ascii=False, indent=2))
# indent=0:换行但不缩进
print(json.dumps(data, ensure_ascii=False, indent=0))
# {
# "name": "张三",
# "age": 25
# }
# indent=None(默认):不换行,压缩成一行
print(json.dumps(data, ensure_ascii=False, indent=None))
# {"name": "张三", "age": 25}
面试题 10:什么是 object_hook?请给出使用示例。
标准答案:
object_hook 是 json.loads() 和 json.load() 的一个参数。
作用:每当解析出一个 JSON 对象({})时,
把这个字典传给 object_hook 函数,
用函数的返回值替换原本的字典。
用途:把 JSON 中的特殊结构还原成 Python 对象(如 datetime、自定义类等)。
import json
from datetime import datetime
# 场景:把 JSON 中带有 __type__ 标记的对象还原成 Python 对象
def smart_decoder(obj):
"""自定义解码:识别 __type__ 标记并还原"""
if obj.get("__type__") == "datetime":
return datetime.fromisoformat(obj["value"])
if obj.get("__type__") == "point":
return (obj["x"], obj["y"]) # 还原为元组
return obj # 普通字典原样返回
json_str = '''
{
"event": "用户登录",
"time": {"__type__": "datetime", "value": "2024-01-15T10:30:00"},
"location": {"__type__": "point", "x": 116.4, "y": 39.9}
}
'''
result = json.loads(json_str, object_hook=smart_decoder)
print(result["event"]) # 用户登录
print(result["time"]) # 2024-01-15 10:30:00
print(type(result["time"])) # <class 'datetime.datetime'>
print(result["location"]) # (116.4, 39.9)
print(type(result["location"])) # <class 'tuple'>
面试题 11:如何高效地处理超大 JSON 文件?
标准答案:
对于几十 GB 的大 JSON 文件,直接 json.load() 会把整个文件加载进内存,
可能导致内存不足。常用方案:
1. 如果是 JSON Lines 格式(每行一个 JSON 对象):逐行读取处理
2. 如果是 JSON 数组格式:用 ijson 等第三方库做流式解析
3. 把大文件拆分成小文件(预处理)
import json
# ─── 方案1:JSON Lines 格式(最常用)───
# 文件每行是一个独立的 JSON 对象
# 写入(流式)
with open("big_data.jsonl", "w", encoding="utf-8") as f:
for i in range(1_000_000):
record = {"id": i, "value": i * 2}
f.write(json.dumps(record) + "n")
# 读取(逐行,内存占用极低)
total = 0
count = 0
with open("big_data.jsonl", "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if line:
record = json.loads(line)
total += record["value"]
count += 1
print(f"共 {count} 条,value 总和:{total}")
# ─── 方案2:分块读取 JSON 数组(用第三方 ijson 库)───
# pip install ijson
# import ijson
# with open("huge.json", "rb") as f:
# for item in ijson.items(f, "item"): # "item" 代表数组中的每个元素
# process(item)
面试题 12:以下代码会输出什么?请解释原因。
题目:
import json
s = '{"a": 1, "a": 2, "a": 3}'
result = json.loads(s)
print(result)
print(result["a"])
参考答案:
import json
s = '{"a": 1, "a": 2, "a": 3}'
result = json.loads(s)
print(result) # {'a': 3}
print(result["a"]) # 3
解释:
JSON 规范中,对象的键重复是"未定义行为"(各实现可以自己决定)。
Python 的 json.loads() 遵循字典的规则:后面的值覆盖前面的值。
所以 "a" 的最终值是最后一个 3。
这是一个需要注意的点:
- JSON 规范没有禁止重复键,但大多数库选择使用最后一个
- 在实际开发中,重复键通常是 bug,需要避免
面试题 13:ensure_ascii=False 和 ensure_ascii=True 的区别?什么时候用哪个?
标准答案:
import json
data = {"城市": "北京", "name": "test"}
# ensure_ascii=True(默认):
# 所有非 ASCII 字符(包括中文、日文、特殊符号等)都转义为 uXXXX
# 生成的 JSON 只包含 ASCII 字符,任何编码方式都能安全存储和传输
s1 = json.dumps(data)
print(s1)
# {"city": "u5317u4eac", "name": "test"}
# ensure_ascii=False:
# 中文等字符直接输出,可读性更好,但写文件时需确保编码正确
s2 = json.dumps(data, ensure_ascii=False)
print(s2)
# {"城市": "北京", "name": "test"}
使用建议:
| 场景 | 建议 |
|---|---|
| 写入文件(指定了 utf-8 编码) | ensure_ascii=False(更可读) |
| 网络传输(HTTP API) | ensure_ascii=False(减少体积) |
| 写入编码不确定的文件 | ensure_ascii=True(最安全) |
| 调试打印 | ensure_ascii=False(方便阅读) |
面试题 14:如何用 JSON 实现一个简单的缓存装饰器?
标准答案:
import json
import os
import hashlib
import functools
def json_cache(cache_file="cache.json"):
"""
把函数的计算结果缓存到 JSON 文件中。
参数相同的调用直接返回缓存结果,不重复计算。
"""
def decorator(func):
# 加载已有缓存
if os.path.exists(cache_file):
with open(cache_file, "r", encoding="utf-8") as f:
cache = json.load(f)
else:
cache = {}
@functools.wraps(func)
def wrapper(*args, **kwargs):
# 生成唯一 key(函数名 + 参数的哈希)
key_data = json.dumps({"func": func.__name__,
"args": args,
"kwargs": kwargs},
sort_keys=True)
key = hashlib.md5(key_data.encode()).hexdigest()
if key in cache:
print(f"[缓存命中] {func.__name__}{args}")
return cache[key]
# 计算并缓存
print(f"[计算中] {func.__name__}{args}")
result = func(*args, **kwargs)
cache[key] = result
with open(cache_file, "w", encoding="utf-8") as f:
json.dump(cache, f, ensure_ascii=False, indent=2)
return result
return wrapper
return decorator
@json_cache("math_cache.json")
def slow_power(base, exp):
"""模拟耗时计算"""
import time
time.sleep(0.5)
return base ** exp
# 第一次调用:计算并缓存
print(slow_power(2, 10)) # [计算中] 1024
print(slow_power(3, 5)) # [计算中] 243
# 第二次调用:从缓存读取
print(slow_power(2, 10)) # [缓存命中] 1024(0.5秒变0秒)
print(slow_power(3, 5)) # [缓存命中] 243
面试题 15:综合考查——写一个 JSON 数据合并工具
题目: 编写函数 merge_json_files(file_list, output_file),把多个 JSON 文件(每个都是字典列表)合并成一个文件,并去除重复的记录(根据 id 字段判重)。
参考答案:
import json
import os
def merge_json_files(file_list, output_file, id_field="id"):
"""
合并多个 JSON 文件(每个文件是一个 JSON 数组)
根据 id_field 去重,保留最后出现的记录
"""
merged = {} # id → record 的字典(自动去重,后者覆盖前者)
total_read = 0
for filepath in file_list:
if not os.path.exists(filepath):
print(f"[警告] 文件不存在,跳过:{filepath}")
continue
try:
with open(filepath, "r", encoding="utf-8") as f:
records = json.load(f)
if not isinstance(records, list):
print(f"[警告] 文件格式不是数组,跳过:{filepath}")
continue
for record in records:
if id_field not in record:
print(f"[警告] 记录缺少 '{id_field}' 字段:{record}")
continue
merged[record[id_field]] = record
total_read += 1
print(f"已读取 {filepath},{len(records)} 条记录")
except json.JSONDecodeError as e:
print(f"[错误] 解析失败 {filepath}:{e}")
# 按 id 排序后写入
final_list = sorted(merged.values(), key=lambda r: r[id_field])
with open(output_file, "w", encoding="utf-8") as f:
json.dump(final_list, f, ensure_ascii=False, indent=2)
print(f"n合并完成:读取 {total_read} 条,去重后 {len(final_list)} 条")
print(f"输出文件:{output_file}")
return final_list
# 测试:先创建两个示例文件
data1 = [
{"id": 1, "name": "张三", "score": 85},
{"id": 2, "name": "李四", "score": 90},
{"id": 3, "name": "王五", "score": 78},
]
data2 = [
{"id": 2, "name": "李四", "score": 95}, # 重复,score 更新了
{"id": 4, "name": "赵六", "score": 88},
{"id": 5, "name": "钱七", "score": 92},
]
with open("part1.json", "w", encoding="utf-8") as f:
json.dump(data1, f, ensure_ascii=False, indent=2)
with open("part2.json", "w", encoding="utf-8") as f:
json.dump(data2, f, ensure_ascii=False, indent=2)
# 合并
result = merge_json_files(["part1.json", "part2.json"], "merged.json")
print("n合并结果:")
for r in result:
print(f" id={r['id']} {r['name']} {r['score']}分")
输出:
已读取 part1.json,3 条记录
已读取 part2.json,3 条记录
合并完成:读取 6 条,去重后 5 条
输出文件:merged.json
合并结果:
id=1 张三 85分
id=2 李四 95分 ← score 被 part2 的更新覆盖
id=3 王五 78分
id=4 赵六 88分
id=5 钱七 92分
知识点速查
| 题号 | 类型 | 考查点 |
|---|---|---|
| 练习1 | 基础 | JSON 格式合法性判断(单引号、尾随逗号、裸键名等) |
| 练习2 | 基础 | dumps 三大参数:ensure_ascii、indent、sort_keys |
| 练习3 | 基础 | loads 解析 JSON 数组,结合列表推导式和 max/sum |
| 练习4 | 综合 | dump/load 读写文件,实现持久化存储 |
| 练习5 | 基础 | 安全取值:.get() 处理缺失字段和默认值 |
| 练习6 | 找错 | 四大常见错误:load vs loads、编码、ensure_ascii、异常处理 |
| 练习7 | 进阶 | default 参数和自定义 JSONEncoder,处理 datetime |
| 练习8 | 综合 | 完整的 JSON 数据库系统(增删查、统计分析) |
| 面试1 | 概念 | dumps/dump/loads/load 四函数的区别与联系 |
| 面试2 | 概念 | Python ↔ JSON 类型映射表(含特殊情况) |
| 面试3 | 实践 | 中文乱码的两个根源及正确解决方案 |
| 面试4 | 实践 | JSONDecodeError 异常及其属性(msg/pos/lineno/colno) |
| 面试5 | 实践 | datetime 序列化的三种方式 |
| 面试6 | 代码题 | 整数键序列化后变字符串这个坑 |
| 面试7 | 代码题 | tuple→list 类型丢失、True/None 的 JSON 对应 |
| 面试8 | 进阶 | JSON 序列化实现深拷贝的方法与局限 |
| 面试9 | 细节 | indent 参数传整数 vs 传字符串的区别 |
| 面试10 | 进阶 | object_hook 自定义解码器的原理与用法 |
| 面试11 | 进阶 | 大文件处理策略:JSON Lines + 逐行读取 |
| 面试12 | 代码题 | JSON 对象重复键的处理规则 |
| 面试13 | 概念 | ensure_ascii 的取舍与使用场景 |
| 面试14 | 综合 | JSON 缓存装饰器(结合第1章装饰器知识) |
| 面试15 | 综合 | 多文件合并去重,完整错误处理 |