《Python JSON 数据解析全指南:从基础到实战(含 jsonpath 与 Schema 验证)》
JSON数据解析
1.JSON基础概念
JSON 是一种轻量级的数据交换格式(另一个叫XML),具有简洁、易读的特点,并且在不同编程语言之间能很好地实现数据传递。在 Python 中,json模块能够实现 Python 数据类型与 JSON 数据格式之间的相互转换。
JSON的语法规则
- 数据以键值对形式表示:
"name": "张三"
- 数据由逗号分隔
- 花括号 {} 保存对象
- 方括号 [] 保存数组
- 支持的数据类型:
- 数字(整数或浮点数)
- 字符串(双引号中)
- 布尔值(true 或 false)
- 数组(方括号中)
- 对象(花括号中)
- null(None)
JSON 类型 | Python 类型 |
---|---|
对象 | dict |
数组 | list/tuple |
字符串 | str |
数字 | int/float |
true | True |
false | False |
null | None |
JSON数据示例
{"name": "张三","age": 30,"isStudent": false,"courses": ["Python", "数据分析", "机器学习"],"address": {"city": "北京","postcode": "100000"},"phoneNumbers": null
}
json模块中的主要函数
函数 | 描述 |
---|---|
json.dumps() | 将Python对象编码成JSON字符串 |
json.dump() | 将Python对象编码成JSON字符串并写入文件 |
json.loads() | 将JSON字符串解码为Python对象 |
json.load() | 从文件中读取JSON字符串并解码为Python对象 |
2.JSON数据编码(序列化)
在实际开发中,为了让生成的 JSON 字符串更易读,可以使用indent参数进行缩进格式化,使用sort_keys参数按键名进行排序。
import json# 字典对象
data = {"name": "张三","age": 30,"isStudent": False,"courses": ["Python", "数据分析", "机器学习"],"address": {"city": "北京","postcode": "100000"},"phoneNumbers": None
}
# 将Python对象编码为JSON字符串
json_str = json.dumps(data, ensure_ascii=False)
print(json_str)
# 格式化输出
json_str = json.dumps(data, indent=4, ensure_ascii=False)
print(json_str)
json_str = json.dumps(data, indent=4, sort_keys=True, ensure_ascii=False)
print(json_str)# 写入到文件中
with open("data.json", "w", encoding="utf-8") as file:json.dump(data, file, ensure_ascii=False, indent=4, sort_keys=True)
3.JSON数据解码(反序列化)
import json
json_str = '{"name": "张三", "age": 30, "isStudent": false, "courses": ["Python", "数据分析", "机器学习"], "address": {"city": "北京", "postcode": "100000"}, "phoneNumbers": null}'
data = json.loads(json_str)
print(type(data))
print(data)
print(data['name'])
print(data['courses'][1])with open("data.json", 'r', encoding='utf-8') as file:data = json.load(file)print(data)print(type(data))
4.自定义编码器与解码器
标准的JSON只支持以下数据类型:
- 字符串(string)
- 数字(number)
- 对象(object)- 在Python中表示为字典
- 数组(array)- 在Python中表示为列表
- 布尔值(boolean)- 在Python中表示为True/False
- 空值(null)- 在Python中表示为None
然而,Python中有许多其他数据类型(如日期时间、自定义类、集合等)无法直接被JSON序列化。这就是为什么我们需要自定义编码器和解码器。
import json
import datetime
data = {"name": "张三","create_at": datetime.datetime.now(),"tags": {"Pythion", "JSON", "编程"}
}json_str = json.dumps(data, indent=4)
print(json_str)
# TypeError: Object of type set is not JSON serializable
自定义编码器
就是将Python中不能够直接编码为JSON格式的数据进行转换 转换成能够被JSON识别的数据
(1)使用default参数
最简单的自定义编码方式是使用 json.dumps() 的 default 参数:
import json
import datetime
data = {"name": "张三","create_at": datetime.datetime.now(),"tags": {"Pythion", "JSON", "编程"}
}# 编码函数:将非JSON类型的数据转换为JSON类型的数据
def custom_encoder(obj):if isinstance(obj, datetime.datetime):return obj.isoformat()elif isinstance(obj, set):return list(obj)json_str = json.dumps(data, indent=4, default=custom_encoder, ensure_ascii=False)
print(json_str)
(2)创建JSONEncoder子类
更灵活的方法是继承 json.JSONEncoder 类并重写 default 方法:
import json
import datetime
data = {"name": "张三","create_at": datetime.datetime.now(),"tags": {"Pythion", "JSON", "编程"}
}# 了解
class CustomJSONEncoder(json.JSONEncoder):def default(self, obj):if isinstance(obj, datetime.datetime):return {"__datetime__":True,"value":obj.isoformat()}elif isinstance(obj, set):return {"__set__": True, # 将数据原本的类型进行录入 为了给解码做提示"value":list(obj)}return super().default(obj)json_str = json.dumps(data, indent=4, cls=CustomJSONEncoder, ensure_ascii=False)
print(json_str)
自定义解码器
就是将某些JSON数据解码为原先的样子(Python数据类型)
(1)使用object_hook参数
json.loads() 函数的 object_hook 参数允许我们自定义JSON对象的解码方式:
import json
import datetime
json_str = """
{"name": "张三","create_at": {"__datetime__": true,"value": "2025-06-09T19:59:05.343916"},"tags": {"__set__": true,"value": ["编程","JSON","Pythion"]}
}
"""
# 自定义解码函数
def custom_decoder(obj):if "__datetime__" in obj:return datetime.datetime.fromisoformat(obj['value'])elif "__set__" in obj:return set(obj['value'])return objdata = json.loads(json_str, object_hook=custom_decoder)
print(data)
(2)创建JSONDecoder子类
更复杂的解码可以通过继承 json.JSONDecoder 类实现:
import json
import datetime
json_str = """
{"name": "张三","create_at": {"__datetime__": true,"value": "2025-06-09T19:59:05.343916"},"tags": {"__set__": true,"value": ["编程","JSON","Pythion"]}
}
"""
# 自定义解码类
class CustomJSONDecoder(json.JSONDecoder):# 构造函数def __init__(self, *args, **kwargs):json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs)def object_hook(self,obj):if "__datetime__" in obj:return datetime.datetime.fromisoformat(obj['value'])elif "__set__" in obj:return set(obj['value'])return objdata = json.loads(json_str, cls=CustomJSONDecoder)
print(data)
5.处理复杂JSON数据
嵌套结构
{"company": "ABC科技","employees": [{"id": 1001,"name": "张三","department": "研发","skills": ["Python", "Django", "Docker"]},{"id": 1002,"name": "李四","department": "数据","skills": ["Python", "数据分析", "机器学习"]}],"address": {"city": "北京","street": "中关村","postcode": "100080"}
}
import json
json_str = '''
{"company": "ABC科技","employees": [{"id": 1001,"name": "张三","department": "研发","skills": ["Python", "Django", "Docker"]},{"id": 1002,"name": "李四","department": "数据","skills": ["Python", "数据分析", "机器学习"]}],"address": {"city": "北京","street": "中关村","postcode": "100080"}
}
'''
data = json.loads(json_str)
print(data['company'])
print(data['employees'][0]['name'])
print(data['employees'][1]['skills'][1])
for employee in data['employees']:print(employee['name'])print(employee['department'])print(employee['skills'])
动态解析路径
import json
json_str = '''
{"company": "ABC科技","employees": [{"id": 1001,"name": "张三","department": "研发","skills": ["Python", "Django", "Docker"]},{"id": 1002,"name": "李四","department": "数据","skills": ["Python", "数据分析", "机器学习"]}],"address": {"city": "北京","street": "中关村","postcode": "100080"}
}
'''
data = json.loads(json_str)path1 = "employees.0.name"
path2 = "address.city"def get_value_by_path(data, path):keys = path.split(".")result = datafor key in keys:if key.isdigit():result = result[int(key)]else:result = result[key]return resultprint(get_value_by_path(data, path1))
print(get_value_by_path(data, path2))
print(get_value_by_path(data, "employees.1.skills.2"))
6.拓展:jsonpath库
JSONPath提供了一种简洁的方式来导航JSON结构并提取特定数据,而不需要复杂的循环和条件语句。
pip install jsonpath-ng
基本语法元素
语法元素 | 描述 |
---|---|
$ | 根对象/元素 |
@ | 当前对象/元素 |
. | 子元素操作符 |
.. | 递归下降操作符 |
* | 通配符,匹配所有对象/元素 |
[] | 下标操作符 |
[,] | 并集操作符 |
[start:end:step] | 数组切片操作符 |
?() | 过滤表达式 |
() | 脚本表达式 |
示例JSON数据
{"store": {"book": [{"category": "参考书","author": "李明","title": "Python编程入门","price": 49.99},{"category": "小说","author": "王芳","title": "梦想之旅","price": 29.99,"isbn": "0-553-21311-3"}],"bicycle": {"color": "红色","price": 599.99}}
}
示例路径演示
JSONPath | 描述 |
---|---|
$.store.book[*].author | 所有书籍的作者 |
$..author | 所有作者 |
$.store.* | store下的所有元素 |
$.store..price | store下所有价格 |
$..book[2] | 第三本书 |
$..book[-1] | 最后一本书 |
$..book[0,1] | 前两本书 |
$..book[:2] | 前两本书 |
$..book[?(@.isbn)] | 所有有isbn属性的书 |
$..book[?(@.price<10)] | 所有价格小于10的书 |
$..book[?(@.price==8.95)] | 所有价格等于8.95的书 |
$..book[?(@.title =~ /.*REGEX.*/i)] | 标题匹配正则表达式的书 |
基本操作
import json
from jsonpath_ng import parse
json_str = """
{"store": {"book": [{"category": "参考书","author": "李明","title": "Python编程入门","price": 49.99},{"category": "小说","author": "王芳","title": "梦想之旅","price": 29.99,"isbn": "0-553-21311-3"}],"bicycle": {"color": "红色","price": 599.99}}
} """
data = json.loads(json_str)
# 导航对象
jsonpath_expr = parse('$.store.book[*].author')print(jsonpath_expr.find(data))# 列表
print(jsonpath_expr.find(data)[0]) # DatumInContext对象
print(type(jsonpath_expr.find(data)[0]))
for match in jsonpath_expr.find(data):print(match.value)print(match.path)print(match.context)
7.拓展:Schema验证
JSONSchema是一种用于验证JSON数据结构的规范,它允许我们定义JSON数据的格式、类型和约束条件,确保数据符合预期的结构和规则。
pip install jsonschema
JSONSchema是一种基于JSON格式的模式语言,用于:
- 描述现有数据格式
- 提供清晰的人机可读文档
- 验证数据,确保数据质量
- 自动测试和验证客户端提交的数据
核心关键字
关键字 | 描述 |
---|---|
$schema | 指定Schema的版本 |
title | Schema的标题 |
description | Schema的描述 |
type | 定义值的数据类型 |
properties | 定义对象的属性 |
required | 指定必需的属性 |
minimum /maximum | 数值的最小/最大值 |
minLength /maxLength | 字符串的最小/最大长度 |
pattern | 字符串必须匹配的正则表达式 |
enum | 枚举,值必须是指定的值之一 |
from jsonschema import validate# 定义一个JSON验证Schema
# 用户信息验证
user_schema = {"type": "object", # 要求JSON整体是个JSON对象"properties": { # 指定属性要求"name": {"type": "string"}, # 必须有一个name属性 string类型"age": {"type": "integer", "minimum": 0}, # 必须有一个age属性 int类型 最小0"email":{"type": "string", "format":"email"},"interests": {"type":"array", "items":{"type":"string"}}},"required":["name","age","email"]
}
user1 = {"name":"张三","age":30,"email":"zhangsan@qq.com","interests":["编程", "吃饭"]
}
user2 = {"name":"张三","age":30,"interests":["编程", "吃饭"]
}
user3 = {"name":"张三","age":30,"email":"zhangsan@qq.com",
}
user4 = {"name":"张三","age":"三十","email":"zhangsan@qq.com",
}
# 最好try-except
validate(instance=user1, schema=user_schema)
# validate(instance=user2, schema=user_schema) # 缺email
validate(instance=user3, schema=user_schema)
validate(instance=user4, schema=user_schema)
8.案例:头条新闻
import requestsdef print_news(title, uniquekey):# 1512-新闻详情查询 - 代码参考(根据实际业务情况修改)# 基本参数配置apiUrl = 'http://v.juhe.cn/toutiao/content' # 接口请求URLapiKey = '3719af094e850cf3f4c4aea0bdb361d6' # 在个人中心->我的数据,接口名称上方查看# 接口请求入参配置requestParams = {'key': apiKey,'uniquekey': uniquekey,}# 发起接口网络请求response = requests.get(apiUrl, params=requestParams)# 解析响应结果if response.status_code == 200:data = response.json()# 网络请求成功。可依据业务逻辑和接口文档说明自行处理。print("=" * 20)print(title)print(data['result']['content'])else:# 网络异常等因素,解析结果异常。可依据业务逻辑自行处理。print('请求异常')def search_news():# 923-新闻列表查询 - 代码参考(根据实际业务情况修改)# 基本参数配置apiUrl = 'http://v.juhe.cn/toutiao/index' # 接口请求URLapiKey = '3719af094e850cf3f4c4aea0bdb361d6' # 在个人中心->我的数据,接口名称上方查看# 接口请求入参配置requestParams = {'key': apiKey,'type': 'yule','page': 1,'page_size': 10,'is_filter': 0,}# 发起接口网络请求response = requests.get(apiUrl, params=requestParams)# 解析响应结果if response.status_code == 200: # 404data = response.json()# 网络请求成功。可依据业务逻辑和接口文档说明自行处理。# print(data)# print(type(data))for item in data['result']['data']:print_news(item['title'], item['uniquekey'])else:# 网络异常等因素,解析结果异常。可依据业务逻辑自行处理。print('请求异常')search_news()