use llm_json::{loads, RepairOptions};
use serde_json::Value;
pub fn repair_to_value(input: &str) -> anyhow::Result<Value> {
match serde_json::from_str::<Value>(input) {
Ok(value) => Ok(value),
Err(strict_err) => loads(input, &RepairOptions::default()).map_err(|repair_err| {
anyhow::anyhow!(
"failed to parse JSON even after repair: strict error = {strict_err}; \
repair error = {repair_err}"
)
}),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_already_valid_json_unchanged() {
let value = repair_to_value(r#"{"name":"qwen","dim":384}"#).unwrap();
assert_eq!(value["name"], "qwen");
assert_eq!(value["dim"], 384);
}
#[test]
fn repairs_unquoted_keys_and_trailing_comma() {
let value = repair_to_value(r#"{name: 'John', age: 30,}"#).unwrap();
assert_eq!(value["name"], "John");
assert_eq!(value["age"], 30);
}
#[test]
fn repairs_markdown_fenced_payload() {
let fenced = "```json\n{\"entities\": [\"rust\", \"sqlite\"]}\n```";
let value = repair_to_value(fenced).unwrap();
assert_eq!(value["entities"][0], "rust");
assert_eq!(value["entities"][1], "sqlite");
}
#[test]
fn coerces_non_json_text_into_a_value() {
let text = repair_to_value("this is not json at all <<<").unwrap();
assert_eq!(
text,
Value::String("this is not json at all <<<".to_string())
);
let empty = repair_to_value("").unwrap();
assert_eq!(empty, serde_json::json!({}));
}
}