22
33from __future__ import annotations
44
5+ import json
56from collections .abc import AsyncGenerator , Generator , Iterable
67
78
89def extract_json_from_codeblock (content : str ) -> str :
9- """Extract the first JSON object-like span from a text block."""
10- first_brace = content .find ("{" )
11- last_brace = content .rfind ("}" )
12- if first_brace != - 1 and last_brace != - 1 :
13- return content [first_brace : last_brace + 1 ]
10+ """Extract the first JSON object- or array-like span from a text block."""
11+ for start_index , start_char in enumerate (content ):
12+ if start_char not in "{[" :
13+ continue
14+
15+ end_stack = ["}" if start_char == "{" else "]" ]
16+ in_string = False
17+ escape_next = False
18+
19+ for end_index in range (start_index + 1 , len (content )):
20+ char = content [end_index ]
21+
22+ if escape_next :
23+ escape_next = False
24+ elif char == "\\ " and in_string :
25+ escape_next = True
26+ elif char == '"' :
27+ in_string = not in_string
28+
29+ if in_string :
30+ continue
31+
32+ if char in "{[" :
33+ end_stack .append ("}" if char == "{" else "]" )
34+ continue
35+ if end_stack and char == end_stack [- 1 ]:
36+ end_stack .pop ()
37+ if not end_stack :
38+ candidate = content [start_index : end_index + 1 ]
39+ try :
40+ json .loads (candidate )
41+ except Exception :
42+ break
43+ return candidate
44+
1445 return content
1546
1647
@@ -21,7 +52,7 @@ def extract_json_from_stream(chunks: Iterable[str]) -> Generator[str, None, None
2152 json_started = False
2253 in_string = False
2354 escape_next = False
24- brace_stack : list [str ] = []
55+ delimiter_stack : list [str ] = []
2556 buffer : list [str ] = []
2657 codeblock_buffer : list [str ] = []
2758
@@ -47,21 +78,21 @@ def extract_json_from_stream(chunks: Iterable[str]) -> Generator[str, None, None
4778 if codeblock_delimiter_count > 0 :
4879 codeblock_delimiter_count = 0
4980
50- if char == "{" :
81+ if char in "{[ " :
5182 json_started = True
52- brace_stack .append ("{ " )
83+ delimiter_stack .append ("}" if char == "{" else "] " )
5384 buffer .append (char )
5485 continue
5586
5687 if json_started :
57- if char == '"' and not escape_next :
58- in_string = not in_string
88+ if escape_next :
89+ escape_next = False
5990 elif char == "\\ " and in_string :
6091 escape_next = True
6192 buffer .append (char )
6293 continue
63- else :
64- escape_next = False
94+ elif char == '"' :
95+ in_string = not in_string
6596
6697 if in_codeblock and not in_string :
6798 if char == "`" :
@@ -77,11 +108,11 @@ def extract_json_from_stream(chunks: Iterable[str]) -> Generator[str, None, None
77108 codeblock_delimiter_count = 0
78109
79110 if not in_string :
80- if char == "{" :
81- brace_stack .append ("{ " )
82- elif char == "}" and brace_stack :
83- brace_stack .pop ()
84- if not brace_stack :
111+ if char in "{[ " :
112+ delimiter_stack .append ("}" if char == "{" else "] " )
113+ elif delimiter_stack and char == delimiter_stack [ - 1 ] :
114+ delimiter_stack .pop ()
115+ if not delimiter_stack :
85116 buffer .append (char )
86117 yield from buffer
87118 buffer = []
@@ -91,9 +122,9 @@ def extract_json_from_stream(chunks: Iterable[str]) -> Generator[str, None, None
91122 buffer .append (char )
92123 continue
93124
94- if not in_codeblock and not json_started and char == "{" :
125+ if not in_codeblock and not json_started and char in "{[ " :
95126 json_started = True
96- brace_stack .append ("{ " )
127+ delimiter_stack .append ("}" if char == "{" else "] " )
97128 buffer .append (char )
98129
99130 if json_started and buffer :
@@ -109,7 +140,7 @@ async def extract_json_from_stream_async(
109140 json_started = False
110141 in_string = False
111142 escape_next = False
112- brace_stack : list [str ] = []
143+ delimiter_stack : list [str ] = []
113144 buffer : list [str ] = []
114145 codeblock_buffer : list [str ] = []
115146
@@ -135,21 +166,21 @@ async def extract_json_from_stream_async(
135166 if codeblock_delimiter_count > 0 :
136167 codeblock_delimiter_count = 0
137168
138- if char == "{" :
169+ if char in "{[ " :
139170 json_started = True
140- brace_stack .append ("{ " )
171+ delimiter_stack .append ("}" if char == "{" else "] " )
141172 buffer .append (char )
142173 continue
143174
144175 if json_started :
145- if char == '"' and not escape_next :
146- in_string = not in_string
176+ if escape_next :
177+ escape_next = False
147178 elif char == "\\ " and in_string :
148179 escape_next = True
149180 buffer .append (char )
150181 continue
151- else :
152- escape_next = False
182+ elif char == '"' :
183+ in_string = not in_string
153184
154185 if in_codeblock and not in_string :
155186 if char == "`" :
@@ -166,11 +197,11 @@ async def extract_json_from_stream_async(
166197 codeblock_delimiter_count = 0
167198
168199 if not in_string :
169- if char == "{" :
170- brace_stack .append ("{ " )
171- elif char == "}" and brace_stack :
172- brace_stack .pop ()
173- if not brace_stack :
200+ if char in "{[ " :
201+ delimiter_stack .append ("}" if char == "{" else "] " )
202+ elif delimiter_stack and char == delimiter_stack [ - 1 ] :
203+ delimiter_stack .pop ()
204+ if not delimiter_stack :
174205 buffer .append (char )
175206 for buffered_char in buffer :
176207 yield buffered_char
@@ -181,9 +212,9 @@ async def extract_json_from_stream_async(
181212 buffer .append (char )
182213 continue
183214
184- if not in_codeblock and not json_started and char == "{" :
215+ if not in_codeblock and not json_started and char in "{[ " :
185216 json_started = True
186- brace_stack .append ("{ " )
217+ delimiter_stack .append ("}" if char == "{" else "] " )
187218 buffer .append (char )
188219
189220 if json_started and buffer :
0 commit comments