1use serde::de::DeserializeOwned;
11
12use crate::error::{
13 ensure_input_within_limits, record_extracted_span, truncate, ParseError, ParseOptions,
14 ParseTrace,
15};
16use crate::extract::{
17 extract_code_block, extract_code_block_for, find_bracketed_limited, preprocess_opts,
18};
19use crate::repair::try_repair_json;
20
21pub fn parse_json<T: DeserializeOwned>(response: &str) -> Result<T, ParseError> {
53 let opts = ParseOptions::default();
54 let (result, _trace) = parse_json_with_trace::<T>(response, &opts)?;
55 Ok(result)
56}
57
58pub fn parse_json_value(response: &str) -> Result<serde_json::Value, ParseError> {
62 parse_json(response)
63}
64
65pub fn parse_json_with_trace<T: DeserializeOwned>(
75 response: &str,
76 opts: &ParseOptions,
77) -> Result<(T, ParseTrace), ParseError> {
78 ensure_input_within_limits(response, opts)?;
79
80 let mut trace = ParseTrace::default();
81 let (candidate, cleaned) = extract_json_candidate_traced(response, opts, &mut trace)?;
82
83 trace.strategies_tried.push("deserialize_candidate");
85 let deser_err = match serde_json::from_str::<T>(&candidate) {
86 Ok(val) => return Ok((val, trace)),
87 Err(e) => e.to_string(),
88 };
89
90 let mut repair_attempts = 0;
92 if repair_attempts < opts.max_repair_attempts {
93 trace.strategies_tried.push("repair_candidate");
94 repair_attempts += 1;
95 if let Some(repaired) = try_repair_json(&candidate) {
96 trace.repaired = true;
97 trace.repair_actions.push("repaired_candidate".to_string());
98 if let Ok(val) = serde_json::from_str::<T>(&repaired) {
99 return Ok((val, trace));
100 }
101 }
102 }
103
104 if candidate != cleaned && repair_attempts < opts.max_repair_attempts {
106 trace.strategies_tried.push("repair_cleaned");
107 repair_attempts += 1;
108 if let Some(repaired) = try_repair_json(&cleaned) {
109 trace.repaired = true;
110 trace.repair_actions.push("repaired_cleaned".to_string());
111 if let Ok(val) = serde_json::from_str::<T>(&repaired) {
112 return Ok((val, trace));
113 }
114 }
115 }
116
117 let _ = repair_attempts;
119
120 Err(ParseError::DeserializationFailed {
122 reason: deser_err,
123 raw_json: truncate(&candidate, 200),
124 })
125}
126
127pub fn parse_json_value_with_trace(
131 response: &str,
132 opts: &ParseOptions,
133) -> Result<(serde_json::Value, ParseTrace), ParseError> {
134 parse_json_with_trace(response, opts)
135}
136
137fn extract_json_candidate_traced(
139 response: &str,
140 opts: &ParseOptions,
141 trace: &mut ParseTrace,
142) -> Result<(String, String), ParseError> {
143 let trimmed = response.trim();
144
145 if trimmed.is_empty() {
146 return Err(ParseError::EmptyResponse);
147 }
148
149 let cleaned = preprocess_opts(trimmed, opts.strip_think_tags);
150
151 if cleaned.is_empty() {
152 return Err(ParseError::EmptyResponse);
153 }
154
155 trace.strategies_tried.push("direct_parse");
157 if serde_json::from_str::<serde_json::Value>(&cleaned).is_ok() {
158 trace.extracted_span = Some((0, cleaned.len()));
159 return Ok((cleaned.clone(), cleaned));
160 }
161
162 if opts.allow_code_fences {
163 trace.strategies_tried.push("json_code_block");
165 if let Some(content) = extract_code_block_for(&cleaned, "json") {
166 record_extracted_span(trace, &cleaned, content);
167 if serde_json::from_str::<serde_json::Value>(content).is_ok() {
168 return Ok((content.to_string(), cleaned));
169 }
170 return Ok((content.to_string(), cleaned));
172 }
173
174 trace.strategies_tried.push("any_code_block");
176 if let Some((_lang, content)) = extract_code_block(&cleaned) {
177 let trimmed_content = content.trim();
179 if trimmed_content.starts_with('{') || trimmed_content.starts_with('[') {
180 record_extracted_span(trace, &cleaned, trimmed_content);
181 if serde_json::from_str::<serde_json::Value>(trimmed_content).is_ok() {
182 return Ok((trimmed_content.to_string(), cleaned));
183 }
184 return Ok((trimmed_content.to_string(), cleaned));
185 }
186 }
187 }
188
189 trace.strategies_tried.push("bracket_match_object");
191 if let Some(bracket_str) = find_bracketed_limited(&cleaned, '{', '}', opts.max_nesting_depth)? {
192 record_extracted_span(trace, &cleaned, bracket_str);
193 if serde_json::from_str::<serde_json::Value>(bracket_str).is_ok() {
194 return Ok((bracket_str.to_string(), cleaned));
195 }
196 return Ok((bracket_str.to_string(), cleaned));
197 }
198
199 trace.strategies_tried.push("bracket_match_array");
201 if let Some(bracket_str) = find_bracketed_limited(&cleaned, '[', ']', opts.max_nesting_depth)? {
202 record_extracted_span(trace, &cleaned, bracket_str);
203 if serde_json::from_str::<serde_json::Value>(bracket_str).is_ok() {
204 return Ok((bracket_str.to_string(), cleaned));
205 }
206 return Ok((bracket_str.to_string(), cleaned));
207 }
208
209 Ok((cleaned.clone(), cleaned))
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216 use serde::Deserialize;
217
218 #[derive(Debug, Deserialize, PartialEq)]
219 struct Kv {
220 key: String,
221 }
222
223 #[derive(Debug, Deserialize, PartialEq)]
224 struct Sentiment {
225 sentiment: String,
226 }
227
228 #[derive(Debug, Deserialize, PartialEq)]
229 struct Outer {
230 outer: Inner,
231 }
232
233 #[derive(Debug, Deserialize, PartialEq)]
234 struct Inner {
235 inner: Vec<i32>,
236 }
237
238 #[test]
239 fn direct_json_object() {
240 let input = r#"{"key": "value"}"#;
241 let result: Kv = parse_json(input).unwrap();
242 assert_eq!(result.key, "value");
243 }
244
245 #[test]
246 fn direct_json_array() {
247 let input = "[1, 2, 3]";
248 let result: Vec<i32> = parse_json(input).unwrap();
249 assert_eq!(result, vec![1, 2, 3]);
250 }
251
252 #[test]
253 fn think_then_json() {
254 let input = r#"<think>analyzing</think>{"key": "value"}"#;
255 let result: Kv = parse_json(input).unwrap();
256 assert_eq!(result.key, "value");
257 }
258
259 #[test]
260 fn code_block_json() {
261 let input = "Here's the data:\n```json\n{\"key\": \"value\"}\n```";
262 let result: Kv = parse_json(input).unwrap();
263 assert_eq!(result.key, "value");
264 }
265
266 #[test]
267 fn bare_code_block() {
268 let input = "```\n{\"key\": \"value\"}\n```";
269 let result: Kv = parse_json(input).unwrap();
270 assert_eq!(result.key, "value");
271 }
272
273 #[test]
274 fn json_in_prose() {
275 let input = r#"The analysis is {"sentiment": "positive"} as shown."#;
276 let result: Sentiment = parse_json(input).unwrap();
277 assert_eq!(result.sentiment, "positive");
278 }
279
280 #[test]
281 fn nested_json() {
282 let input = r#"{"outer": {"inner": [1,2,3]}}"#;
283 let result: Outer = parse_json(input).unwrap();
284 assert_eq!(result.outer.inner, vec![1, 2, 3]);
285 }
286
287 #[test]
288 fn repaired_trailing_comma() {
289 let input = r#"{"key": "value",}"#;
290 let result: Kv = parse_json(input).unwrap();
291 assert_eq!(result.key, "value");
292 }
293
294 #[test]
295 fn repaired_single_quotes() {
296 let input = "{'key': 'value'}";
297 let result: Kv = parse_json(input).unwrap();
298 assert_eq!(result.key, "value");
299 }
300
301 #[test]
302 fn think_and_code_block() {
303 let input = "<think>hmm</think>\n```json\n{\"key\": \"value\"}\n```";
304 let result: Kv = parse_json(input).unwrap();
305 assert_eq!(result.key, "value");
306 }
307
308 #[test]
309 fn json_with_surrounding_text() {
310 let input = "Sure! Here's your result: {\"key\": \"value\"}\nHope that helps!";
311 let result: Kv = parse_json(input).unwrap();
312 assert_eq!(result.key, "value");
313 }
314
315 #[test]
316 fn parse_json_value_works() {
317 let input = r#"{"a": 1, "b": "two"}"#;
318 let val = parse_json_value(input).unwrap();
319 assert_eq!(val["a"], 1);
320 assert_eq!(val["b"], "two");
321 }
322
323 #[test]
324 fn empty_response_fails() {
325 let result: Result<Kv, _> = parse_json("");
326 assert!(result.is_err());
327 }
328
329 #[test]
332 fn parse_json_value_with_trace_direct() {
333 let input = r#"{"a": 1}"#;
334 let opts = ParseOptions::default();
335 let (val, trace) = parse_json_value_with_trace(input, &opts).unwrap();
336 assert_eq!(val["a"], 1);
337 assert!(trace.strategies_tried.contains(&"direct_parse"));
338 assert!(!trace.repaired);
339 }
340
341 #[test]
342 fn parse_json_with_trace_repair() {
343 let input = "{'key': 'value'}";
344 let opts = ParseOptions::default();
345 let (val, trace): (Kv, ParseTrace) = parse_json_with_trace(input, &opts).unwrap();
346 assert_eq!(val.key, "value");
347 assert!(trace.repaired);
348 assert!(!trace.repair_actions.is_empty());
349 }
350
351 #[test]
352 fn parse_json_with_trace_code_block() {
353 let input = "Here:\n```json\n{\"key\": \"value\"}\n```";
354 let opts = ParseOptions::default();
355 let (val, trace): (Kv, ParseTrace) = parse_json_with_trace(input, &opts).unwrap();
356 assert_eq!(val.key, "value");
357 assert!(trace.strategies_tried.contains(&"json_code_block"));
358 }
359
360 #[test]
363 fn too_large_input() {
364 let input = "x".repeat(100);
365 let opts = ParseOptions {
366 max_input_bytes: 50,
367 ..Default::default()
368 };
369 let result = parse_json_value_with_trace(&input, &opts);
370 match result {
371 Err(ParseError::TooLarge {
372 size: 100,
373 limit: 50,
374 }) => {}
375 other => panic!("expected TooLarge, got {other:?}"),
376 }
377 }
378
379 #[test]
380 fn too_large_exact_boundary() {
381 let input = r#"{"a":1}"#;
383 let opts = ParseOptions {
384 max_input_bytes: input.len(),
385 ..Default::default()
386 };
387 let result = parse_json_value_with_trace(input, &opts);
388 assert!(result.is_ok());
389 }
390
391 #[test]
392 fn too_deep_nesting() {
393 let mut nested = String::new();
396 for _ in 0..5 {
397 nested.push_str(r#"{"a":"#);
398 }
399 nested.push('1');
400 for _ in 0..5 {
401 nested.push('}');
402 }
403 let input = format!("Here is the result: {nested}");
404 let opts = ParseOptions {
405 max_nesting_depth: 3,
406 ..Default::default()
407 };
408 let result = parse_json_value_with_trace(&input, &opts);
409 match result {
410 Err(ParseError::TooDeep { .. }) => {}
411 other => panic!("expected TooDeep, got {other:?}"),
412 }
413 }
414
415 #[test]
416 fn too_deep_exact_boundary() {
417 let input = r#"Here: {"a": {"b": 1}}"#; let opts = ParseOptions {
420 max_nesting_depth: 2,
421 ..Default::default()
422 };
423 let result = parse_json_value_with_trace(input, &opts);
424 assert!(result.is_ok());
425 }
426
427 #[test]
428 fn strip_think_tags_option_disabled() {
429 let input = r#"<think>{"key": "wrong"}</think>{"key": "right"}"#;
430 let opts = ParseOptions {
431 strip_think_tags: false,
432 ..Default::default()
433 };
434 let (val, _trace) = parse_json_value_with_trace(input, &opts).unwrap();
436 assert_eq!(val["key"], "right");
438 }
439
440 #[test]
441 fn code_fences_option_disabled() {
442 let input = "```json\n{\"key\": \"fenced\"}\n```\n{\"key\": \"bare\"}";
444 let opts = ParseOptions {
445 allow_code_fences: false,
446 ..Default::default()
447 };
448 let (val, trace): (Kv, ParseTrace) = parse_json_with_trace(input, &opts).unwrap();
449 assert!(!trace.strategies_tried.contains(&"json_code_block"));
451 assert!(!trace.strategies_tried.contains(&"any_code_block"));
452 assert_eq!(val.key, "bare");
453 }
454}