在现代数据交换中,JSON(JavaScript Object Notation)已成为最流行的数据格式之一。它轻量级、易于阅读和编写,并且被几乎所有编程语言支持。然而,在实际应用中,我们常常遇到需要转换JSON格式的情况:
- 不同系统间的数据结构不一致
- API版本升级导致的数据结构变化
- 数据清洗和预处理需求
- 优化数据存储结构
- 适应不同前端框架的数据要求
Python凭借其简洁的语法和强大的数据处理能力,成为JSON格式转换的理想工具。本教程将带你全面掌握Python处理JSON的各种技巧。
Python处理JSON的基础
Python标准库中的json
模块提供了处理JSON数据的基本功能。下面是核心的四个方法:
方法 | 描述 | 使用场景 |
---|---|---|
json.load() |
从文件对象读取JSON数据并转换为Python对象 | 读取JSON文件 |
json.loads() |
将JSON字符串解析为Python对象 | 处理API响应中的JSON字符串 |
json.dump() |
将Python对象转换为JSON并写入文件 | 保存处理结果到文件 |
json.dumps() |
将Python对象转换为JSON字符串 | 准备发送给API的JSON数据 |
基本读写操作示例
import json
# 读取JSON文件
with open('data.json', 'r', encoding='utf-8') as f:
data = json.load(f)
# 修改数据
data['new_key'] = 'new_value'
# 写入JSON文件(格式化输出)
with open('modified_data.json', 'w', encoding='utf-8') as f:
json.dump(data, f, indent=4, ensure_ascii=False)
indent
:指定缩进空格数,使输出更易读(设置为None则不格式化)
ensure_ascii
:设置为False可正确输出非ASCII字符(如中文)
sort_keys
:设置为True可按键名字母顺序排序
常见的JSON转换场景
1. 键名重命名
在不同系统集成时,常常需要将键名改为符合目标系统的命名规范:
# 原始数据
original_data = {
"first_name": "John",
"last_name": "Doe",
"email_addr": "john.doe@example.com"
}
# 键名映射关系
key_mapping = {
"first_name": "firstName",
"last_name": "lastName",
"email_addr": "email"
}
# 转换键名
converted_data = {key_mapping.get(k, k): v for k, v in original_data.items()}
print(converted_data)
# 输出: {'firstName': 'John', 'lastName': 'Doe', 'email': 'john.doe@example.com'}
2. 嵌套结构扁平化
将嵌套的JSON结构转换为扁平结构,便于数据分析:
def flatten_json(data, prefix=''):
flattened = {}
for key, value in data.items():
new_key = f"{prefix}_{key}" if prefix else key
if isinstance(value, dict):
flattened.update(flatten_json(value, new_key))
else:
flattened[new_key] = value
return flattened
# 嵌套结构示例
nested_data = {
"person": {
"name": "Alice",
"age": 30,
"address": {
"street": "123 Main St",
"city": "Boston"
}
},
"company": "TechCorp"
}
# 执行扁平化
flat_data = flatten_json(nested_data)
print(flat_data)
# 输出: {'person_name': 'Alice', 'person_age': 30,
# 'person_address_street': '123 Main St',
# 'person_address_city': 'Boston', 'company': 'TechCorp'}
3. 数据类型转换
处理JSON中不正确的数据类型,如字符串数字转换为数值:
def convert_types(data):
if isinstance(data, dict):
return {k: convert_types(v) for k, v in data.items()}
elif isinstance(data, list):
return [convert_types(item) for item in data]
elif isinstance(data, str):
# 尝试转换为数字
try:
return int(data)
except ValueError:
try:
return float(data)
except ValueError:
# 尝试转换日期等特殊格式
return data
else:
return data
# 原始数据(所有值都是字符串)
data_str = {
"id": "12345",
"price": "29.99",
"count": "100",
"is_active": "true",
"dimensions": {
"width": "15.5",
"height": "20.0"
}
}
converted_data = convert_types(data_str)
print(converted_data)
# 输出: {'id': 12345, 'price': 29.99, 'count': 100,
# 'is_active': 'true', 'dimensions': {'width': 15.5, 'height': 20.0}}
输入JSON
转换后JSON
高级转换技巧
使用Pandas处理复杂转换
对于大型数据集或复杂转换,Pandas提供了强大的处理能力:
import pandas as pd
# 从JSON文件创建DataFrame
df = pd.read_json('employees.json', orient='records')
# 复杂转换示例:
# 1. 拆分全名为姓和名
df[['last_name', 'first_name']] = df['full_name'].str.split(', ', expand=True)
# 2. 转换日期格式
df['start_date'] = pd.to_datetime(df['start_date'], format='%Y-%m-%d')
# 3. 计算工作年限
df['years_of_service'] = (pd.Timestamp.now() - df['start_date']).dt.days // 365
# 4. 按部门分组统计
department_stats = df.groupby('department').agg(
avg_salary=('salary', 'mean'),
employee_count=('id', 'count')
).reset_index()
# 将结果转换为JSON
result_json = department_stats.to_json(orient='records', indent=4)
使用JMESPath进行高级查询和转换
JMESPath提供了类似SQL的查询语法,可以高效地提取和转换JSON数据:
import jmespath
# 复杂JSON数据
company_data = {
"company": "TechInnovate",
"departments": [
{
"name": "Engineering",
"employees": [
{"name": "Alice", "salary": 95000, "role": "Developer"},
{"name": "Bob", "salary": 110000, "role": "Team Lead"}
]
},
{
"name": "Marketing",
"employees": [
{"name": "Charlie", "salary": 85000, "role": "Manager"},
{"name": "Diana", "salary": 78000, "role": "Specialist"}
]
}
]
}
# 使用JMESPath查询:
# 1. 获取所有员工姓名和薪资
expression1 = "departments[*].employees[].[name, salary]"
result1 = jmespath.search(expression1, company_data)
# 结果: [['Alice', 95000], ['Bob', 110000], ['Charlie', 85000], ['Diana', 78000]]
# 2. 获取各部门最高薪资
expression2 = "departments[].[name, max(employees[].salary)]"
result2 = jmespath.search(expression2, company_data)
# 结果: [['Engineering', 110000], ['Marketing', 85000]]
# 3. 获取薪资超过90000的员工
expression3 = "departments[*].employees[?salary > `90000`].name"
result3 = jmespath.search(expression3, company_data)
# 结果: [['Alice', 'Bob']]
最佳实践与性能优化
- 使用ijson流式解析:对于特别大的JSON文件,使用ijson库可以逐项读取数据,减少内存消耗
- 分块处理:将大文件分割为小块分别处理
- 避免不必要的转换:只提取需要的数据,减少中间数据量
- 使用高效的数据结构:如Pandas DataFrame适合表格数据
- 启用C扩展:使用ujson库替代json库,性能提升显著
# 使用ujson提高性能
import ujson
# 读取速度比json快4倍以上
with open('large_file.json', 'r') as f:
data = ujson.load(f)
# 写入速度也比json快
with open('output.json', 'w') as f:
ujson.dump(data, f)
发表评论