use nom::{
IResult, Parser,
branch::alt,
bytes::complete::is_not,
character::complete::{anychar, char},
combinator::recognize,
multi::many0,
sequence::{delimited, pair},
};
use serde::de::DeserializeOwned;
pub fn find_json<T: DeserializeOwned>(input: &str) -> Vec<T> {
candidates(input)
.into_iter()
.filter_map(|s| serde_json::from_str(s).ok())
.collect()
}
fn json_string(i: &str) -> IResult<&str, &str> {
recognize(delimited(
char('"'),
many0(alt((recognize(pair(char('\\'), anychar)), is_not("\"\\")))),
char('"'),
))
.parse(i)
}
fn json_inner(i: &str) -> IResult<&str, &str> {
recognize(many0(alt((
json_string,
json_object,
json_array,
is_not("{}[]\""),
))))
.parse(i)
}
fn json_object(i: &str) -> IResult<&str, &str> {
recognize(delimited(char('{'), json_inner, char('}'))).parse(i)
}
fn json_array(i: &str) -> IResult<&str, &str> {
recognize(delimited(char('['), json_inner, char(']'))).parse(i)
}
fn candidates(input: &str) -> Vec<&str> {
let mut out = Vec::new();
let mut rest = input;
while !rest.is_empty() {
match alt((json_object, json_array)).parse(rest) {
Ok((remaining, matched)) => {
out.push(matched);
rest = remaining;
}
Err(_) => {
let mut chars = rest.chars();
chars.next();
rest = chars.as_str();
}
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use serde::Deserialize;
#[derive(Debug, Deserialize, PartialEq)]
struct Person {
name: String,
age: u32,
}
#[test]
fn single_object() {
let v: Vec<Person> = find_json(r#"Hello ```{"name":"Bob","age":35}``` world"#);
assert_eq!(
v,
[Person {
name: "Bob".into(),
age: 35
}]
);
}
#[test]
fn multiple_objects() {
let v: Vec<Person> =
find_json(r#"{"name":"David","age":30} noise {"name":"Bob","age":25}"#);
assert_eq!(
v,
[
Person {
name: "David".into(),
age: 30
},
Person {
name: "Bob".into(),
age: 25
},
]
);
}
#[test]
fn non_matching_json_skipped() {
let v: Vec<Person> = find_json(r#"{"x":1} {"name":"Alice","age":30}"#);
assert_eq!(
v,
[Person {
name: "Alice".into(),
age: 30
}]
);
}
#[test]
fn nested_struct() {
#[derive(Debug, Deserialize, PartialEq)]
struct Wrapper {
person: Person,
}
let v: Vec<Wrapper> = find_json(r#"{"person":{"name":"Alice","age":30}}"#);
assert_eq!(
v,
[Wrapper {
person: Person {
name: "Alice".into(),
age: 30
}
}]
);
}
#[test]
fn array_of_structs() {
let v: Vec<Vec<Person>> =
find_json(r#"[{"name":"Jason","age":30},{"name":"John","age":25}]"#);
assert_eq!(
v,
[vec![
Person {
name: "Jason".into(),
age: 30
},
Person {
name: "John".into(),
age: 25
},
]]
);
}
#[test]
fn brackets_inside_strings() {
let v: Vec<Person> = find_json(r#"{"name":"A{lice}","age":30}"#);
assert_eq!(
v,
[Person {
name: "A{lice}".into(),
age: 30
}]
);
}
#[test]
fn empty_and_no_json() {
assert!(find_json::<Person>("").is_empty());
assert!(find_json::<Person>("no json here").is_empty());
}
}