ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Python字符串转对象:从JSON到Document解析的三种实战场景

Python字符串转对象:从JSON到Document解析的三种实战场景

摘要

开发中经常遇到"看起来像列表/对象的字符串",怎么转成真正的Python对象?本文从三个场景展开:标准JSON字符串转列表、类列表字符串的安全解析、以及RAG项目中Document对象的元数据提取。读完即用。

一、场景一:标准JSON字符串转列表

这是最简单的情况。字符串本身就是合法的JSON数组。

python

import json raw_str = '["qwen-turbo","qwen-plus","deepseek"]' result = json.loads(raw_str) print(result) print(type(result))

输出:

text

['qwen-turbo', 'qwen-plus', 'deepseek'] <class 'list'>

json.loads()一步到位,干净利落。

也可以用ast.literal_eval

python

import ast raw_str = '["qwen-turbo","qwen-plus","deepseek"]' result = ast.literal_eval(raw_str)

ast.literal_eval更安全,只能解析合法的Python字面量,不会执行恶意代码。不过JSON格式用json.loads更标准。

二、场景二:看起来像列表但实际是对象的字符串

开发中常遇到这种数据——打印出来是[Document(...), Document(...)],看着像列表,实际是一个字符串,不能直接用json.loads解析。

比如RAG检索返回的Document列表被转成字符串打印到日志里:

python

raw_str = '[Document(metadata={"pk": 12, "page": 2}, page_content="内容1"), Document(metadata={"pk": 27, "page": 2}, page_content="内容2")]'

目标:提取每个Document的page属性。

解法:正则提取。

python

import re raw_str = '你的长字符串...' # 提取所有page值 pages = re.findall(r"'page':\s*(\d+)", raw_str) pages = [int(p) for p in pages] print(pages)

输出:

text

[2, 2, 3]

如果想同时提取pagepage_content

python

import re doc_pattern = r"Document\(metadata=\{([^}]+)\}, page_content='([^']*)'" matches = re.findall(doc_pattern, raw_str) for metadata, content in matches: page_match = re.search(r"'page':\s*(\d+)", metadata) if page_match: page = int(page_match.group(1)) print(f"page: {page}, content预览: {content[:50]}...")

关键点:这种字符串的本质是对象的repr()输出,不是标准序列化格式。正则是最直接的处理方式。

三、场景三:更复杂的Document字符串解析

如果字符串中page_content包含换行符、引号等特殊字符,情况会复杂一些。

比如:

python

raw_str = '[Document(metadata={"page": 1}, page_content="包含\'引号\'和\\n换行的内容")]'

简单正则可能匹配失败。这时可以分两步:

  1. 先按Document(分割

  2. 再用括号匹配提取内容

python

import re def extract_docs_from_str(raw_str): # 按Document(分割 parts = raw_str.split('Document(')[1:] results = [] for part in parts: # 找到metadata部分 meta_match = re.search(r"metadata=\{([^}]+)\}", part) if not meta_match: continue metadata_str = meta_match.group(1) page_match = re.search(r"'page':\s*(\d+)", metadata_str) page = int(page_match.group(1)) if page_match else None # 找到page_content部分(从第一个引号到最后一个引号) content_match = re.search(r"page_content='(.*)'\)?\s*$", part, re.DOTALL) if not content_match: content_match = re.search(r'page_content="(.*)"\)?\s*$', part, re.DOTALL) content = content_match.group(1) if content_match else "" results.append({"page": page, "content": content}) return results

注意:对于嵌套引号和转义字符,正则的健壮性有限。如果数据来源可控,建议在数据源头用json.dumps()pickle序列化,不要用repr()打印后再解析。

四、三种方案的对比

场景数据格式推荐方案
JSON数组["a","b"]json.loads()
Python字面量[1,2,3]{"a":1}ast.literal_eval()
对象repr输出[Document(...)]正则提取

五、踩坑提醒

1. 永远不要用eval()

python

# ❌ 危险:可能执行恶意代码 result = eval(raw_str)

eval()会执行字符串中的任意代码。如果数据来自外部输入,攻击者可以注入__import__('os').system('rm -rf /')之类的代码。

2. 区分字符串来源

  • 来自print()打印的对象 → 用正则

  • 来自json.dumps()→ 用json.loads()

  • 来自str(list)repr()→ 用正则或ast.literal_eval()(如果格式合法)

3. 单引号和双引号要统一

json.loads()要求双引号。如果字符串是单引号包裹的JSON,先用replace("'", '"')转换(但要注意内容里的引号不会误转)。

六、总结

方法适用场景安全性
json.loads()标准JSON安全
ast.literal_eval()Python字面量安全
正则提取对象repr输出安全
eval()不推荐任何场景危险

记住一句话:能用json.loads就用json.loads,不能用就上正则,永远别用eval

返回列表