use serde_json::Value;
pub fn parse_or_repair(raw: &str) -> Value {
if raw.trim().is_empty() {
return serde_json::json!({});
}
if let Ok(v) = serde_json::from_str::<Value>(raw) {
return v;
}
if let Some(repaired) = try_repair(raw)
&& let Ok(v) = serde_json::from_str::<Value>(&repaired)
{
tracing::warn!(
"[JSON_REPAIR] recovered partial args ({} bytes → {} bytes): {:?}",
raw.len(),
repaired.len(),
raw.chars()
.rev()
.take(80)
.collect::<String>()
.chars()
.rev()
.collect::<String>()
);
return v;
}
tracing::warn!(
"[JSON_REPAIR] FAILED to recover partial args ({} bytes): {:?}",
raw.len(),
raw.chars().take(200).collect::<String>()
);
serde_json::json!({
"_partial": raw,
"_repair_failed": true,
})
}
pub fn try_repair(raw: &str) -> Option<String> {
let mut chars: Vec<char> = raw.chars().collect();
while chars.last().is_some_and(|c| c.is_whitespace()) {
chars.pop();
}
if chars.is_empty() {
return None;
}
let mut in_string = false;
let mut escape = false;
let mut stack: Vec<char> = Vec::new();
let mut last_complete_value_end: Option<usize> = None;
let mut after_colon = false;
for (i, &c) in chars.iter().enumerate() {
if escape {
escape = false;
continue;
}
if c == '\\' && in_string {
escape = true;
continue;
}
if c == '"' {
in_string = !in_string;
if !in_string {
last_complete_value_end = Some(i);
after_colon = false; }
continue;
}
if in_string {
continue;
}
match c {
'{' | '[' => {
stack.push(c);
after_colon = false; }
'}' => {
if stack.last() == Some(&'{') {
stack.pop();
last_complete_value_end = Some(i);
after_colon = false;
} else {
return None; }
}
']' => {
if stack.last() == Some(&'[') {
stack.pop();
last_complete_value_end = Some(i);
after_colon = false;
} else {
return None;
}
}
':' => after_colon = true,
',' => after_colon = false,
c if !c.is_whitespace() => {
last_complete_value_end = Some(i);
after_colon = false;
}
_ => {}
}
}
let mut out: String = chars.iter().collect();
if in_string {
out.push('"');
}
if after_colon
&& !in_string
&& let Some(end) = last_complete_value_end
{
let bytes = out.as_bytes();
let mut cut = None;
for (i, &b) in bytes.iter().enumerate().take(end + 1).rev() {
if b == b',' {
cut = Some(i);
break;
}
if b == b'{' {
cut = Some(i + 1);
break;
}
}
if let Some(c) = cut {
out.truncate(c);
if out.ends_with(',') {
out.pop();
}
}
}
let trimmed = out.trim_end();
if let Some(stripped) = trimmed.strip_suffix(',') {
out = stripped.to_string();
}
while let Some(open) = stack.pop() {
match open {
'{' => out.push('}'),
'[' => out.push(']'),
_ => {}
}
}
Some(out)
}