add support for quoted path

This commit is contained in:
Ihor Andrianov
2025-02-09 19:12:09 +02:00
parent f84a6b878a
commit ee16c49c6c
2 changed files with 84 additions and 21 deletions

View File

@@ -24,13 +24,15 @@ pub struct JsonPath<'a> {
pub elements: Vec<PathElement<'a>>,
}
type IsQuoted = bool;
/// PathElement describes a single element of a JSON path.
#[derive(Clone, Debug, PartialEq)]
pub enum PathElement<'a> {
/// Root element: '$'
Root(),
/// JSON key
Key(Cow<'a, str>),
Key(Cow<'a, str>, IsQuoted),
/// Array locator, eg. [2], [#-5]
ArrayLocator(i32),
}
@@ -73,12 +75,14 @@ pub fn json_path<'a>(path: &'a str) -> crate::Result<JsonPath<'a>> {
}
let mut parser_state = PPState::Start;
let mut index_state = ArrayIndexState::Start;
let mut is_quoted = false;
let mut key_start = 0;
let mut index_buffer: i128 = 0;
let mut path_components = Vec::with_capacity(estimate_path_capacity(path));
let mut path_iter = path.char_indices();
while let Some(ch) = path_iter.next() {
let ch_len = ch.1.len_utf8();
match parser_state {
@@ -103,9 +107,17 @@ pub fn json_path<'a>(path: &'a str) -> crate::Result<JsonPath<'a>> {
},
PPState::InKey => match ch {
(idx, '.' | '[') => {
if is_quoted {
continue;
}
let key_end = idx;
if key_end > key_start {
let key = &path[key_start..key_end];
let mut key = &path[key_start..key_end];
println!("{}, {}", &key[0..2], &key[key.len() - 2..]);
if key[0..2].contains("\"") && key[key.len() - 2..].contains("\"") {
key = &key[2..key.len() - 2];
}
if ch.1 == '[' {
index_state = ArrayIndexState::Start;
parser_state = PPState::InArrayIndex;
@@ -113,12 +125,45 @@ pub fn json_path<'a>(path: &'a str) -> crate::Result<JsonPath<'a>> {
} else {
key_start = idx + ch_len;
}
path_components.push(PathElement::Key(Cow::Borrowed(key)));
path_components.push(PathElement::Key(Cow::Borrowed(key), is_quoted));
is_quoted = false;
} else {
bail_parse_error!("Bad json path: {}", path)
}
}
(_, _) => continue,
(idx, ch) => {
if ch != '"' {
continue;
};
if key_start == idx {
is_quoted = true
} else {
if let Some(next_char) = path_iter.next() {
let c = next_char.1;
match next_char {
(idx, '.' | '[') => {
let key_end = idx;
if key_end > key_start {
let key = &path[key_start + 1..key_end - 1];
if c == '[' {
index_state = ArrayIndexState::Start;
parser_state = PPState::InArrayIndex;
index_buffer = 0;
} else {
key_start = idx + c.len_utf8();
}
path_components
.push(PathElement::Key(Cow::Borrowed(key), is_quoted));
}
is_quoted = false;
}
_ => bail_parse_error!("Bad json path: {}", path),
}
}
}
}
},
PPState::InArrayIndex => {
let (_, c) = ch;
@@ -178,16 +223,19 @@ pub fn json_path<'a>(path: &'a str) -> crate::Result<JsonPath<'a>> {
PPState::InArrayIndex => bail_parse_error!("Bad json path: {}", path),
PPState::InKey => {
if key_start < path.len() {
let key = &path[key_start..];
let mut key = &path[key_start..];
path_components.push(PathElement::Key(Cow::Borrowed(key)));
if key[0..=1].contains("\"") && key[key.len() - 1..].contains("\"") {
key = &key[1..key.len() - 1];
}
path_components.push(PathElement::Key(Cow::Borrowed(key), is_quoted));
} else {
bail_parse_error!("Bad json path: {}", path)
}
}
_ => (),
}
println!("{:?}", path_components);
Ok(JsonPath {
elements: path_components,
})
@@ -209,7 +257,10 @@ mod tests {
let path = json_path("$.x").unwrap();
assert_eq!(path.elements.len(), 2);
assert_eq!(path.elements[0], PathElement::Root());
assert_eq!(path.elements[1], PathElement::Key(Cow::Borrowed("x")));
assert_eq!(
path.elements[1],
PathElement::Key(Cow::Borrowed("x"), false)
);
}
#[test]
@@ -251,10 +302,19 @@ mod tests {
let path = json_path("$.store.book[0].title").unwrap();
assert_eq!(path.elements.len(), 5);
assert_eq!(path.elements[0], PathElement::Root());
assert_eq!(path.elements[1], PathElement::Key(Cow::Borrowed("store")));
assert_eq!(path.elements[2], PathElement::Key(Cow::Borrowed("book")));
assert_eq!(
path.elements[1],
PathElement::Key(Cow::Borrowed("store"), false)
);
assert_eq!(
path.elements[2],
PathElement::Key(Cow::Borrowed("book"), false)
);
assert_eq!(path.elements[3], PathElement::ArrayLocator(0));
assert_eq!(path.elements[4], PathElement::Key(Cow::Borrowed("title")));
assert_eq!(
path.elements[4],
PathElement::Key(Cow::Borrowed("title"), false)
);
}
#[test]
@@ -274,7 +334,10 @@ mod tests {
assert_eq!(path.elements[1], PathElement::ArrayLocator(0));
assert_eq!(path.elements[2], PathElement::ArrayLocator(1));
assert_eq!(path.elements[3], PathElement::ArrayLocator(2));
assert_eq!(path.elements[4], PathElement::Key(Cow::Borrowed("key")));
assert_eq!(
path.elements[4],
PathElement::Key(Cow::Borrowed("key"), false)
);
assert_eq!(path.elements[5], PathElement::ArrayLocator(3));
}

View File

@@ -394,7 +394,7 @@ fn json_extract_single<'a>(
PathElement::Root() => {
current_element = json;
}
PathElement::Key(key) => match current_element {
PathElement::Key(key, _) => match current_element {
Val::Object(map) => {
if let Some((_, value)) = map.iter().find(|(k, _)| k == key) {
current_element = value;
@@ -442,7 +442,7 @@ fn json_path_from_owned_value(path: &OwnedValue, strict: bool) -> crate::Result<
JsonPath {
elements: vec![
PathElement::Root(),
PathElement::Key(Cow::Borrowed(t.as_str())),
PathElement::Key(Cow::Borrowed(t.as_str()), false),
],
}
}
@@ -454,7 +454,7 @@ fn json_path_from_owned_value(path: &OwnedValue, strict: bool) -> crate::Result<
OwnedValue::Float(f) => JsonPath {
elements: vec![
PathElement::Root(),
PathElement::Key(Cow::Owned(f.to_string())),
PathElement::Key(Cow::Owned(f.to_string()), false),
],
},
_ => crate::bail_constraint_error!("JSON path error near: {:?}", path.to_string()),
@@ -501,7 +501,7 @@ fn find_target<'a>(json: &'a mut Val, path: &JsonPath) -> Option<Target<'a>> {
return None;
}
},
PathElement::Key(key) => match current {
PathElement::Key(key, _) => match current {
Val::Object(obj) => {
if let Some(pos) = &obj
.iter()
@@ -577,7 +577,7 @@ fn find_or_create_target<'a>(json: &'a mut Val, path: &JsonPath) -> Option<Targe
*current = Val::Array(vec![]);
}
},
PathElement::Key(key) => match current {
PathElement::Key(key, _) => match current {
Val::Object(obj) => {
if let Some(pos) = &obj
.iter()
@@ -1253,7 +1253,7 @@ mod tests {
fn test_find_target_object() {
let mut val = Val::Object(vec![("key".to_string(), Val::String("value".to_string()))]);
let path = JsonPath {
elements: vec![PathElement::Key(Cow::Borrowed("key"))],
elements: vec![PathElement::Key(Cow::Borrowed("key"), false)],
};
match find_target(&mut val, &path) {
@@ -1269,7 +1269,7 @@ mod tests {
("key".to_string(), Val::String("value".to_string())),
]);
let path = JsonPath {
elements: vec![PathElement::Key(Cow::Borrowed("key"))],
elements: vec![PathElement::Key(Cow::Borrowed("key"), false)],
};
match find_target(&mut val, &path) {
@@ -1364,7 +1364,7 @@ mod tests {
let result = result.unwrap();
match &result.elements[..] {
[PathElement::Root(), PathElement::Key(field)] if *field == "field" => {}
[PathElement::Root(), PathElement::Key(field, false)] if *field == "field" => {}
_ => panic!("Expected root and field"),
}
}
@@ -1433,7 +1433,7 @@ mod tests {
let result = result.unwrap();
match &result.elements[..] {
[PathElement::Root(), PathElement::Key(field)] if *field == "1.23" => {}
[PathElement::Root(), PathElement::Key(field, false)] if *field == "1.23" => {}
_ => panic!("Expected root and field"),
}
}