use serde::de::DeserializeOwned;
pub fn json_slice<T: DeserializeOwned>(text: &str) -> Option<T> {
let slice = balanced_slice(text)?;
serde_json::from_str::<T>(slice).ok()
}
fn balanced_slice(text: &str) -> Option<&str> {
let bytes = text.as_bytes();
let start = bytes.iter().position(|&b| b == b'[' || b == b'{')?;
let open = bytes[start];
let close = if open == b'[' { b']' } else { b'}' };
let mut depth = 0u32;
let mut in_string = false;
let mut escaped = false;
for (offset, &byte) in bytes[start..].iter().enumerate() {
if in_string {
in_string = step_string(&mut escaped, byte);
} else if scan_structural(byte, open, close, &mut in_string, &mut depth) {
return Some(&text[start..=start + offset]);
}
}
None
}
fn scan_structural(byte: u8, open: u8, close: u8, in_string: &mut bool, depth: &mut u32) -> bool {
if byte == b'"' {
*in_string = true;
} else if byte == open {
*depth += 1;
} else if byte == close {
*depth = depth.saturating_sub(1);
return *depth == 0;
}
false
}
fn step_string(escaped: &mut bool, byte: u8) -> bool {
match (*escaped, byte) {
(true, _) => {
*escaped = false;
true
}
(false, b'\\') => {
*escaped = true;
true
}
(false, b'"') => false,
(false, _) => true,
}
}
pub fn normalize(text: &str) -> String {
text.split_whitespace()
.collect::<Vec<_>>()
.join(" ")
.to_lowercase()
}
pub fn tokens(text: &str) -> Vec<String> {
text.to_lowercase()
.split(|c: char| !c.is_alphanumeric())
.filter(|w| !w.is_empty())
.map(str::to_string)
.collect()
}