MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Python正则表达式处理JSON数据

2024-07-094.8k 阅读

Python 正则表达式与 JSON 数据基础

Python 正则表达式基础

正则表达式是一种描述字符模式的工具,在 Python 中通过 re 模块来使用。它能够高效地进行字符串匹配、查找、替换等操作。

在 Python 中,re 模块提供了一系列函数,如 re.search()re.match()re.findall() 等。re.search() 函数会在整个字符串中搜索匹配的模式,只要找到一个匹配就返回 Match 对象,否则返回 None。例如:

import re
text = "Python is a great language, Python is fun"
match = re.search(r'Python', text)
if match:
    print(f"找到匹配: {match.group()}")

这里,r'Python' 是正则表达式模式,r 前缀表示这是一个原始字符串,避免反斜杠在 Python 字符串中被转义。

re.match() 函数则只在字符串的开头进行匹配。比如:

text = "Python is cool"
match = re.match(r'Python', text)
if match:
    print(f"匹配成功: {match.group()}")

如果字符串开头不是 Pythonre.match() 就会返回 None

re.findall() 函数会找出字符串中所有匹配的模式,并以列表形式返回。例如:

text = "Python is a great language, Python is fun"
matches = re.findall(r'Python', text)
print(f"所有匹配: {matches}")

JSON 数据基础

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。它基于 JavaScript 的一个子集,但现在广泛应用于各种编程语言。

在 Python 中,通过 json 模块来处理 JSON 数据。JSON 数据结构主要有两种类型:对象(在 Python 中对应字典)和数组(在 Python 中对应列表)。

JSON 对象是一个无序的键值对集合,用花括号 {} 包围,键值对之间用逗号 , 分隔,键必须是字符串,值可以是字符串、数字、布尔值、null、对象或数组。例如:

{
    "name": "John",
    "age": 30,
    "city": "New York"
}

在 Python 中,可以将其解析为字典:

import json
json_str = '{"name": "John", "age": 30, "city": "New York"}'
data = json.loads(json_str)
print(data["name"])

JSON 数组是一个有序的值序列,用方括号 [] 包围,值之间用逗号 , 分隔,值可以是任何 JSON 数据类型。例如:

["apple", "banana", "cherry"]

在 Python 中,会解析为列表:

json_str = '["apple", "banana", "cherry"]'
data = json.loads(json_str)
print(data[1])

使用正则表达式处理 JSON 数据的场景

从 JSON 字符串中提取特定信息

当面对复杂的 JSON 数据结构时,有时我们可能只需要其中的部分信息。例如,有一个包含用户信息的 JSON 字符串,我们想提取所有用户的名字。假设 JSON 数据如下:

{
    "users": [
        {"name": "Alice", "age": 25},
        {"name": "Bob", "age": 30},
        {"name": "Charlie", "age": 35}
    ]
}

如果不使用 json 模块的标准解析方式,而使用正则表达式,可以这样做:

import re
json_str = '{"users": [{"name": "Alice", "age": 25}, {"name": "Bob", "age": 30}, {"name": "Charlie", "age": 35}]}'
names = re.findall(r'"name":\s*"([^"]+)"', json_str)
print(names)

这里的正则表达式 r'"name":\s*"([^"]+)"' 含义为:先匹配 "name":,然后是零个或多个空白字符 \s*,接着是一个双引号 ",然后 ([^"]+) 表示捕获除双引号外的一个或多个字符,最后再匹配一个双引号 "

验证 JSON 字符串格式

虽然 Python 的 json 模块在解析 JSON 字符串时会检查格式是否正确,但在某些情况下,我们可能希望在解析之前先进行一些简单的格式验证。例如,确保 JSON 字符串至少包含一个键值对。

可以使用正则表达式来检查:

import re
def validate_simple_json(json_str):
    pattern = r'^\s*\{.*\}\s*$'
    return bool(re.match(pattern, json_str))
json_str1 = '{"key": "value"}'
json_str2 = '{'
print(validate_simple_json(json_str1))
print(validate_simple_json(json_str2))

这里的正则表达式 r'^\s*\{.*\}\s*$' 表示:字符串开头 ^,零个或多个空白字符 \s*,然后是左花括号 {,接着是任意字符 .*,再是右花括号 },最后是零个或多个空白字符 \s* 直到字符串结尾 $

正则表达式在 JSON 数据处理中的深入应用

处理嵌套 JSON 结构

对于嵌套的 JSON 结构,提取信息会变得更加复杂。例如,有如下嵌套的 JSON 数据:

{
    "department": "Engineering",
    "teams": [
        {
            "team_name": "Web Dev",
            "members": [
                {"name": "Eve", "role": "Developer"},
                {"name": "Frank", "role": "Designer"}
            ]
        },
        {
            "team_name": "Mobile Dev",
            "members": [
                {"name": "Grace", "role": "Developer"},
                {"name": "Hank", "role": "Tester"}
            ]
        }
    ]
}

如果要提取所有成员的名字,可以使用递归的正则表达式匹配方式。但这种方式比较复杂且不推荐在实际中大量使用,因为 JSON 结构的嵌套深度可能是不确定的,正则表达式难以完全覆盖所有情况。不过为了演示,如下代码:

import re
def extract_member_names(json_str):
    names = []
    def recursive_search(pattern, json_part):
        nonlocal names
        matches = re.findall(pattern, json_part)
        names.extend(matches)
        sub_objects = re.findall(r'\{.*?\}', json_part)
        sub_arrays = re.findall(r'\[.*?\]', json_part)
        for sub_obj in sub_objects:
            recursive_search(pattern, sub_obj)
        for sub_arr in sub_arrays:
            recursive_search(pattern, sub_arr)
    pattern = r'"name":\s*"([^"]+)"'
    recursive_search(pattern, json_str)
    return names
json_str = '{"department": "Engineering", "teams": [{"team_name": "Web Dev", "members": [{"name": "Eve", "role": "Developer"}, {"name": "Frank", "role": "Designer"}]}, {"team_name": "Mobile Dev", "members": [{"name": "Grace", "role": "Developer"}, {"name": "Hank", "role": "Tester"}]}]}'
print(extract_member_names(json_str))

这里通过递归函数 recursive_search,先在当前部分 JSON 字符串中查找成员名字,然后再在子对象和子数组中继续查找。

处理 JSON 数据中的特殊字符

JSON 数据中有时可能包含特殊字符,比如转义字符等。例如:

{
    "message": "This is a \\n newline character example"
}

如果要提取 message 字段的值并处理转义字符,可以结合正则表达式和 json 模块。

import re
import json
json_str = '{"message": "This is a \\n newline character example"}'
match = re.search(r'"message":\s*"([^"]+)"', json_str)
if match:
    value = match.group(1)
    decoded_value = json.loads(f'"{value}"')
    print(decoded_value)

这里先通过正则表达式提取 message 的值,然后利用 json.loads 对值进行处理,以正确解析转义字符。

正则表达式与标准 JSON 处理模块的结合

先正则筛选再 JSON 解析

在处理大量 JSON 数据时,有时可能只想解析其中符合特定条件的部分。例如,有一个文本文件包含多个 JSON 片段,每个片段以特定字符串开头,如 START_JSON

假设文件内容如下:

START_JSON{"name": "Alice", "age": 25}
OTHER_TEXT
START_JSON{"name": "Bob", "age": 30}

可以先使用正则表达式筛选出符合条件的 JSON 片段,然后再进行解析:

import re
import json
file_content = "START_JSON{\"name\": \"Alice\", \"age\": 25}\nOTHER_TEXT\nSTART_JSON{\"name\": \"Bob\", \"age\": 30}"
json_pattern = r'START_JSON(.*?)(?=START_JSON|$)'
matches = re.findall(json_pattern, file_content, re.DOTALL)
for match in matches:
    json_data = json.loads(match)
    print(json_data["name"])

这里 re.DOTALL 标志使 . 可以匹配包括换行符在内的所有字符,(.*?) 是非贪婪匹配,(?=START_JSON|$) 是正向先行断言,确保匹配到下一个 START_JSON 或文件结尾之前的内容。

JSON 解析后用正则处理值

有时在 JSON 解析后,对某些字段的值进行进一步处理时,正则表达式会很有用。例如,解析一个包含用户邮箱的 JSON 数据,然后验证邮箱格式。

{
    "users": [
        {"name": "Alice", "email": "alice@example.com"},
        {"name": "Bob", "email": "bob.example.com"}
    ]
}
import re
import json
json_str = '{"users": [{"name": "Alice", "email": "alice@example.com"}, {"name": "Bob", "email": "bob.example.com"}]}'
data = json.loads(json_str)
email_pattern = r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'
for user in data["users"]:
    if re.match(email_pattern, user["email"]):
        print(f"{user['name']} 的邮箱格式正确")
    else:
        print(f"{user['name']} 的邮箱格式错误")

这里通过正则表达式验证邮箱格式是否符合常见的规范。

实际案例分析

从 API 响应 JSON 中提取特定数据

假设通过 API 获取到如下 JSON 数据,这是一个商品列表,我们想提取所有商品的价格:

{
    "products": [
        {
            "product_name": "Laptop",
            "price": 1000.00,
            "description": "A high - performance laptop"
        },
        {
            "product_name": "Mouse",
            "price": 50.00,
            "description": "A wireless mouse"
        }
    ]
}

使用正则表达式的方式可以这样实现:

import re
json_str = '{"products": [{"product_name": "Laptop", "price": 1000.00, "description": "A high - performance laptop"}, {"product_name": "Mouse", "price": 50.00, "description": "A wireless mouse"}]}'
prices = re.findall(r'"price":\s*([\d.]+)', json_str)
prices = [float(price) for price in prices]
print(prices)

这里的正则表达式 r'"price":\s*([\d.]+)' 用于匹配 price 字段及其对应的值。

对 JSON 日志数据进行预处理

假设有一个 JSON 格式的日志文件,记录了用户的操作,如下:

{
    "timestamp": "2023 - 10 - 01T12:00:00",
    "user": "Alice",
    "action": "login",
    "details": "Successfully logged in"
}

如果要对日志数据进行预处理,比如提取特定时间范围内的日志,可以先使用正则表达式筛选出包含特定时间格式的 JSON 字符串,然后再解析。假设要筛选出 2023 年 10 月 1 日的日志:

import re
import json
log_file_content = '{"timestamp": "2023 - 10 - 01T12:00:00", "user": "Alice", "action": "login", "details": "Successfully logged in"}\n{"timestamp": "2023 - 10 - 02T13:00:00", "user": "Bob", "action": "logout", "details": "Logged out"}'
pattern = r'{"timestamp":\s*"2023 - 10 - 01T.*?}'
matches = re.findall(pattern, log_file_content, re.DOTALL)
for match in matches:
    log_data = json.loads(match)
    print(log_data["user"])

这里通过正则表达式 r'{"timestamp":\s*"2023 - 10 - 01T.*?}' 筛选出 2023 年 10 月 1 日的日志 JSON 字符串,再进行解析。

注意事项与优化

性能问题

使用正则表达式处理 JSON 数据时,性能可能是一个问题。复杂的正则表达式和大量数据会导致处理时间变长。例如,在处理嵌套 JSON 结构时,递归的正则表达式匹配方式效率较低。在实际应用中,应尽量先使用 json 模块进行标准解析,只有在必要时才结合正则表达式。

准确性问题

正则表达式在处理 JSON 数据时,可能因为 JSON 结构的复杂性而导致匹配不准确。例如,在匹配键值对时,如果 JSON 字符串中存在转义字符或特殊格式,可能会导致误匹配。因此,在编写正则表达式时要充分考虑 JSON 数据的各种可能情况,或者尽量使用标准的 JSON 处理方法来保证准确性。

代码可读性与维护性

过多地使用正则表达式处理 JSON 数据会使代码可读性变差,增加维护成本。在编写代码时,应尽量使代码逻辑清晰,将正则表达式的使用限制在合理范围内,并添加适当的注释,以便其他开发者理解代码意图。例如,在上述代码示例中,对每个正则表达式都进行了详细注释,说明其匹配的内容和作用。