当LLM输出包含json结构时,如何解析是一个比较难处理的问题,因为除json外还有常规文本。
虽然prompt约定按```json{xxx}```输出,LLM依然有可能忽略```json ```直接输出json的body。
这里参考网络资料,尝试示例集中健壮性比较好的从字符串提取json的解析方案。
1 直接解析
如果整个字符串就是 JSON,直接使用 json.loads() 解析,示例如下
import json
text = '{"name": "Alice", "age": 30}'
data = json.loads(text)
2 字符串匹配
json body本质上是可解析字符串,所以也可以尝试字符串匹配方法,确定json body范围。
2.1 正则匹配
如果字符串中混杂 JSON 和其他文本,可以采用正则匹配的方法,示例代码如下
import re, json
text = '前面一些文字 {"name": "Bob", "age": 25} 后面还有文字'
match = re.search(r'(\{.*\}|\[.*\])', text, re.DOTALL)
if match:
json_str = match.group(1)
data = json.loads(json_str)
适用于 JSON 不包含嵌套同类型括号,且没有转义干扰。
即无法正确处理嵌套结构,如对象内嵌对象或数组。
此时会匹配到第一个 { 到最后一个 },若中间有其他 {} 可能出错。
然而,实际测试显示,这种方案也能兼容部分此类情况。
嵌套和转义示例如下。
import re, json
text = '前面一些文字 {"name": "Bo{{{b", "age": 25, "d{a{{d": {"dda": true}} 后面还有文字'
match = re.search(r'(\{.*\}|\[.*\])', text, re.DOTALL)
if match:
json_str = match.group(1)
data = json.loads(json_str)
print(data)
输出示例如下
{'name': 'Bo{{{b', 'age': 25, 'd{a{{d': {'dda': True}}
2.2 括号匹配
手动扫描字符串,找到第一个 { 或 [,然后使用计数器匹配对应的闭合括号。
档json比较规范时,括号匹配是一种有效方法。
然而当json的key或value包含{等字符时,该方法失效,示例代码如下。
def extract_json(s):
start = None
stack = []
for i, ch in enumerate(s):
if ch in '{[':
if not stack:
start = i
stack.append(ch)
elif ch in '}]':
if stack and ((ch == '}' and stack[-1] == '{') or (ch == ']' and stack[-1] == '[')):
stack.pop()
if not stack:
return s[start:i+1]
return None
text = '一些文字 {"d{ata": [1,2,3], "ok}{{dsa": true} 结尾'
json_str = extract_json(text)
print(print(text))
if json_str:
data = json.loads(json_str)
print(data)
输出示例如下
一些文字 {"d{ata": [1,2,3], "ok}{{dsa": true} 结尾
None
所以不要仅凭计数 {} 来提取 JSON,除非完全确定字符串内不含花括号。
只要字符串可能包含结构字符,就必须使用真正的 JSON 解析器。
3 标准库方法
字符串匹配方法存在以上所述缺陷,这里进一步尝试标准库方法。
3.1 raw_decode方法
使用json标准库的的raw_decode方法,即用json.JSONDecoder.raw_decode解析。
raw_decode 可以从字符串开头解析 JSON,并返回解析后的对象和结束位置。
raw_decode要求 JSON 必须出现在字符串开头,否则需要先定位。
import json
def extract_json_from_anywhere(s):
decoder = json.JSONDecoder()
# 依次从每个可能的位置尝试解析
for i in range(len(s)):
if s[i] in '{[':
try:
obj, end = decoder.raw_decode(s[i:])
return obj
except json.JSONDecodeError:
continue
return None
text = '前缀 {"valid": true, "dab": {"dd{d": 123, "sta}tus": true}} 后缀'
data = extract_json_from_anywhere(text)
print(data)
输出示例如下,可见此类方法能兼容特殊情况。
{'valid': True, 'dab': {'dd{d': 123, 'sta}tus': True}}
3.2 jsonfinder方法
jsonfinder是专门提取字符串中的 JSON的第三方库。
安装方法如下
pip install jsonfinder
示例代码如下
from jsonfinder import jsonfinder
# 你的原始字符串
text = '前缀 {"name": "Alice", "age": 30}后缀'
# 使用 jsonfinder 迭代提取 JSON 对象
# 每次迭代会返回 (start_index, end_index, parsed_json_object)
for start, end, obj in jsonfinder(text):
# obj 是已经用 json.loads() 解析好的 Python 对象(dict 或 list)
if obj:
print(text[start:end])
print(f"找到了 JSON:{obj}")
输出如下,可见jsonfinder也能从字符串中提取正确的json数据。
{"name": "Alice", "age": 30}
找到了 JSON:{'name': 'Alice', 'age': 30}
reference
jsonfinder
https://github.com/alexmojaki/jsonfinder
在Python中读取大型JSON文件(raw_decode)