extern crate oxidized_json_checker;
#[derive(Debug, PartialEq, Eq)]
pub enum Index {
Array(usize),
Object(String)
}
impl Index {
fn increment(&mut self) {
if let Index::Array(ref mut i) = self {
*i += 1;
}
}
}
impl std::fmt::Display for Index {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", match self {
Index::Array(i) => i.to_string(),
Index::Object(key) => key.to_owned()
})
}
}
#[derive(PartialEq)]
enum Current {
Array,
Object,
None
}
fn end_quote(chars: &Vec<char>, start: usize) -> usize {
let mut i = start;
while i < chars.len() {
if chars[i] == '\\' {
i += 2;
}
if chars[i] == '"' {
break;
}
i += 1;
}
i
}
fn substring(str: &Vec<char>, start: usize, end: usize) -> String {
str.iter().skip(start).take(end-start).collect()
}
pub fn path(text: &str, offset: usize) -> Result<Vec<Index>, oxidized_json_checker::Error> {
oxidized_json_checker::validate_str(&text)?;
let mut pos = 0;
let mut path: Vec<Index> = Vec::new();
let mut in_key = false;
let mut current: Vec<Current> = vec![Current::None];
let chars: Vec<char> = text.chars().collect();
while pos < offset && pos < chars.len() {
let start_pos = pos;
match chars[pos] {
'"' => {
let i = end_quote(&chars, pos+1);
let key = substring(&chars, pos+1, i);
match current.last() {
Some(last) => {
if *last == Current::Object && in_key {
path.push(Index::Object(key));
in_key = false;
pos = i;
}
}
None => {}
}
}
'{' => {
current.push(Current::Object);
in_key = true;
}
'[' => {
path.push(Index::Array(0));
current.push(Current::Array);
}
'}' => {
path.pop();
current.pop();
}
']' => {
path.pop();
current.pop();
}
',' => {
match current.last() {
Some(last) => {
match last {
Current::Object => {
path.pop();
in_key = true;
},
Current::Array => {
let last = path.len()-1;
path[last].increment();
},
Current::None => {}
}
}
None => {}
}
}
_ => ()
}
if pos == start_pos {
pos += 1;
}
}
Ok(path)
}
pub fn dot_path(text: &str, offset: usize) -> Result<String, oxidized_json_checker::Error> {
let p = path(text, offset)?;
Ok(dots(&p))
}
fn dots(p: &Vec<Index>) -> String {
let mut dotted = "$".to_owned();
for i in p {
dotted += ".";
dotted += &match i {
Index::Array(i) => i.to_string(),
Index::Object(key) => key.to_owned()
}
}
dotted
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let json = r#"[9, {"field1": "b", "field2": [null, null, 87, 4], "field3": "file.txt"}]"#;
let vec_path = path(json, json.find("87").unwrap()).expect("Invalid JSON");
assert_eq!(vec_path, vec![Index::Array(1), Index::Object(String::from("field2")), Index::Array(2)]);
let dotted = dot_path(json, json.find("87").unwrap()).expect("Invalid JSON");
assert_eq!(dotted, "$.1.field2.2");
assert_eq!(path(json, 1000).unwrap(), vec![]);
}
}