21.Python JSON 练习题与笔试面试题(含答案)

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 字符串,要求:

  1. 中文直接显示(不转义)
  2. 4 个空格缩进
  3. 键名按字母排序
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,
    "科目": [
        "数学",
        "语文",
        "英语"
    ]
}

关键点:

  • Truetrue(小写),Nonenull,这是 Python 和 JSON 的重要区别
  • sort_keys=True 按键名的字典序排序(汉字按 Unicode 码点)

3. loads 基础解析

题目: 解析下面的 JSON 字符串,然后:

  1. 打印所有学生的姓名
  2. 计算平均分
  3. 找出成绩最高的学生
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.json
  • complete_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() 会报错。用两种方式解决:

  1. default 参数(函数方式)
  2. 自定义 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
setbytesdatetime ❌ 无法直接序列化 需要 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_asciiindentsort_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 综合 多文件合并去重,完整错误处理

发表评论