diff --git a/core/json/json_path.rs b/core/json/json_path.rs index 3e325c6bd..210945a23 100644 --- a/core/json/json_path.rs +++ b/core/json/json_path.rs @@ -24,13 +24,15 @@ pub struct JsonPath<'a> { pub elements: Vec>, } +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> { } 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> { }, 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> { } 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> { 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)); } diff --git a/core/json/mod.rs b/core/json/mod.rs index bb05adfe8..4984a80d5 100644 --- a/core/json/mod.rs +++ b/core/json/mod.rs @@ -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> { 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 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"), } }