Improve jsonpath support

This commit is contained in:
Fabrice Reix 2021-10-05 17:05:00 +02:00 committed by Fabrice Reix
parent dab4006346
commit 9093325d28
6 changed files with 688 additions and 142 deletions

View File

@ -26,13 +26,24 @@ pub struct Query {
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Selector {
Wildcard,
NameChild(String),
ArrayIndex(usize),
ArrayIndex(Vec<usize>), // one or more indexes (separated by comma)
ArraySlice(Slice),
ArrayWildcard,
Filter(Predicate),
RecursiveWildcard,
RecursiveKey(String),
}
// For the time-being
// use simple slice start:end (without the step)
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Slice {
pub start: Option<i64>,
pub end: Option<i64>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Predicate {
pub key: String,

View File

@ -37,20 +37,50 @@ impl Query {
impl Selector {
pub fn eval(self, root: serde_json::Value) -> JsonpathResult {
match self {
Selector::NameChild(field) => match root.get(field) {
None => vec![],
Some(value) => vec![value.clone()],
},
Selector::ArrayIndex(index) => match root.get(index) {
None => vec![],
Some(value) => vec![value.clone()],
},
Selector::ArrayWildcard {} => {
Selector::Wildcard | Selector::ArrayWildcard => {
let mut elements = vec![];
if let serde_json::Value::Array(values) = root {
for value in values {
elements.push(value);
}
} else if let serde_json::Value::Object(key_values) = root {
for value in key_values.values() {
elements.push(value.clone());
}
}
elements
}
Selector::NameChild(field) => match root.get(field) {
None => vec![],
Some(value) => vec![value.clone()],
},
Selector::ArrayIndex(indexes) => {
let mut values = vec![];
for index in indexes {
if let Some(value) = root.get(index) {
values.push(value.clone())
}
}
values
}
Selector::ArraySlice(Slice { start, end }) => {
let mut elements = vec![];
if let serde_json::Value::Array(values) = root {
for (i, value) in values.iter().enumerate() {
if let Some(n) = start {
let n = if n < 0 { values.len() as i64 + n } else { n };
if (i as i64) < n {
continue;
}
}
if let Some(n) = end {
let n = if n < 0 { values.len() as i64 + n } else { n };
if (i as i64) >= n {
continue;
}
}
elements.push(value.clone());
}
}
elements
}
@ -86,6 +116,30 @@ impl Selector {
}
elements
}
Selector::RecursiveWildcard => {
let mut elements = vec![];
match root {
serde_json::Value::Object(map) => {
for elem in map.values() {
elements.push(elem.clone());
for element in Selector::RecursiveWildcard.eval(elem.clone()) {
elements.push(element);
}
}
}
serde_json::Value::Array(values) => {
for elem in values {
elements.push(elem.clone());
for element in Selector::RecursiveWildcard.eval(elem.clone()) {
elements.push(element);
}
}
}
_ => {}
}
elements
}
}
}
}
@ -95,6 +149,7 @@ impl Predicate {
match elem {
serde_json::Value::Object(ref obj) => {
match (obj.get(self.key.as_str()), self.func.clone()) {
(Some(_), PredicateFunc::KeyExist {}) => true,
(Some(serde_json::Value::Number(v)), PredicateFunc::Equal(ref num)) => {
approx_eq!(f64, v.as_f64().unwrap(), num.to_f64(), ulps = 2)
} //v.as_f64().unwrap() == num.to_f64(),
@ -137,7 +192,6 @@ mod tests {
json!({
"book": json_books(),
"bicycle": [
]
})
}
@ -153,23 +207,25 @@ mod tests {
pub fn json_first_book() -> serde_json::Value {
json!({
"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
})
"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
})
}
pub fn json_second_book() -> serde_json::Value {
json!( { "category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
json!({
"category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
})
}
pub fn json_third_book() -> serde_json::Value {
json!( { "category": "fiction",
json!({
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
@ -178,11 +234,12 @@ mod tests {
}
pub fn json_fourth_book() -> serde_json::Value {
json!({ "category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
json!({
"category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
})
}
@ -205,7 +262,7 @@ mod tests {
selectors: vec![
Selector::NameChild("store".to_string()),
Selector::NameChild("book".to_string()),
Selector::ArrayIndex(0),
Selector::ArrayIndex(vec![0]),
Selector::NameChild("title".to_string()),
],
};
@ -271,9 +328,13 @@ mod tests {
#[test]
pub fn test_selector_array_index() {
assert_eq!(
Selector::ArrayIndex(0).eval(json_books()),
Selector::ArrayIndex(vec![0]).eval(json_books()),
vec![json_first_book()]
);
assert_eq!(
Selector::ArrayIndex(vec![1, 2]).eval(json_books()),
vec![json_second_book(), json_third_book()]
);
}
#[test]
@ -290,37 +351,19 @@ mod tests {
}
#[test]
pub fn test_bookstore() {
// assert_eq!(Selector::NameChild("store".to_string()).eval(json_root()),
// vec![json_store()]
// );
// assert_eq!(Selector::NameChild("book".to_string()).eval(json_store()),
// vec![json_books()]
// );
//
// assert_eq!(Selector::ArrayIndex(0).eval(json_books()),
// vec![json_first_book()]
// );
//
// assert_eq!(Selector::NameChild("title".to_string()).eval(json_first_book()),
// vec![json!("Sayings of the Century")]
// );
//
// assert_eq!(Selector::ArrayIndex(0).eval(json_books()),
// vec![json_first_book()]
// );
// assert_eq!(Selector::Filter(Predicate::KeyEqualStringValue("category".to_string(), "reference".to_string())).eval(json_books()),
// vec![json_first_book()]
// );
//
// assert_eq!(Selector::Filter(Predicate::KeySmallerThanIntValue("price".to_string(), 10)).eval(json_books()),
// vec![json_first_book(), json_third_book()]
// );
//
// assert_eq!(Selector::RecursiveKey("book".to_string()).eval(json_root()),
// vec![json_books()]
// );
pub fn test_selector_array_slice() {
assert_eq!(
Selector::ArraySlice(Slice {
start: None,
end: Some(2)
})
.eval(json_books()),
vec![json_first_book(), json_second_book(),]
);
}
#[test]
pub fn test_recursive_key() {
assert_eq!(
Selector::RecursiveKey("author".to_string()).eval(json_root()),
vec![
@ -336,11 +379,19 @@ mod tests {
#[test]
pub fn test_array_index() {
let value = json!(["first", "second", "third", "forth", "fifth"]);
assert_eq!(Selector::ArrayIndex(2).eval(value), vec![json!("third")]);
assert_eq!(
Selector::ArrayIndex(vec![2]).eval(value),
vec![json!("third")]
);
}
#[test]
pub fn test_predicate() {
assert!(Predicate {
key: "key".to_string(),
func: PredicateFunc::KeyExist {},
}
.eval(json!({"key": "value"})));
assert!(Predicate {
key: "key".to_string(),
func: PredicateFunc::EqualString("value".to_string()),

View File

@ -16,6 +16,34 @@
*
*/
/*
* jsonpath specs
* There is no proper specifications for jsonpath.
* The defacto one is still https://goessner.net/articles/JsonPath/
* Hurl will try to follow this one as closely as possible
*
* There are a few edge cases for which several implementations differ
* The online app https://jsonpath.herokuapp.com/ might be used to test them
* We describe below the behaviour that we expect in Hurl.
*
* Specify a field key in a subscript operator: $['name']
* The key must be enclosed within single quotes only.
* The following expressions will not be valid: $["name"] and $[name]
*
* Accessing a key containing a single quote must be escape: $['\'']
* Key with unicode are supported: $['✈']
*
* Any character within these quote won't have a specific meaning:
* $['*'] selects the element with key '*'. It is different from $[*] which selects all elements
* $['.'] selects the element with key '.'.
*
* The dot notation is usually more readable the the bracket notation
* but it is more limited in terms of allowed characters
* The following characters are allowed:
* alphanumeric
* _ (underscore)
*/
pub use self::parser::parse;
mod ast;

View File

@ -46,12 +46,15 @@ fn query(reader: &mut Reader) -> ParseResult<Query> {
fn selector(reader: &mut Reader) -> ParseResult<Selector> {
choice(
vec![
selector_filter,
selector_wildcard,
selector_recursive_wildcard,
selector_recursive_key,
selector_array_index,
selector_array_wildcard,
selector_array_slice,
selector_object_key_bracket,
selector_object_key,
selector_filter,
],
reader,
)
@ -59,6 +62,7 @@ fn selector(reader: &mut Reader) -> ParseResult<Selector> {
fn selector_array_index(reader: &mut Reader) -> Result<Selector, Error> {
try_left_bracket(reader)?;
let mut indexes = vec![];
let i = match natural(reader) {
Err(e) => {
return Err(Error {
@ -69,8 +73,28 @@ fn selector_array_index(reader: &mut Reader) -> Result<Selector, Error> {
}
Ok(v) => v,
};
indexes.push(i);
loop {
let state = reader.state.clone();
if try_literal(",", reader).is_ok() {
let i = match natural(reader) {
Err(e) => {
return Err(Error {
pos: e.pos,
recoverable: true,
inner: e.inner,
})
}
Ok(v) => v,
};
indexes.push(i);
} else {
reader.state = state;
break;
}
}
literal("]", reader)?;
Ok(Selector::ArrayIndex(i))
Ok(Selector::ArrayIndex(indexes))
}
fn selector_array_wildcard(reader: &mut Reader) -> Result<Selector, Error> {
@ -80,6 +104,37 @@ fn selector_array_wildcard(reader: &mut Reader) -> Result<Selector, Error> {
Ok(Selector::ArrayWildcard {})
}
fn selector_array_slice(reader: &mut Reader) -> Result<Selector, Error> {
try_left_bracket(reader)?;
let state = reader.state.clone();
let start = match integer(reader) {
Err(_) => {
reader.state = state.clone();
None
}
Ok(v) => Some(v),
};
if try_literal(":", reader).is_err() {
return Err(Error {
pos: state.pos,
recoverable: true,
inner: ParseError::Expecting {
value: ":".to_string(),
},
});
};
let state = reader.state.clone();
let end = match integer(reader) {
Err(_) => {
reader.state = state;
None
}
Ok(v) => Some(v),
};
literal("]", reader)?;
Ok(Selector::ArraySlice(Slice { start, end }))
}
fn selector_filter(reader: &mut Reader) -> Result<Selector, Error> {
try_literal("[?(", reader)?;
let pred = predicate(reader)?;
@ -119,7 +174,7 @@ fn selector_object_key(reader: &mut Reader) -> Result<Selector, Error> {
});
};
let s = reader.read_while(|c| c.is_alphanumeric());
let s = reader.read_while(|c| c.is_alphanumeric() || *c == '_');
if s.is_empty() {
return Err(Error {
pos: reader.state.pos.clone(),
@ -136,6 +191,16 @@ fn selector_object_key(reader: &mut Reader) -> Result<Selector, Error> {
Ok(Selector::NameChild(s))
}
fn selector_wildcard(reader: &mut Reader) -> Result<Selector, Error> {
try_literal(".*", reader)?;
Ok(Selector::Wildcard {})
}
fn selector_recursive_wildcard(reader: &mut Reader) -> Result<Selector, Error> {
try_literal("..*", reader)?;
Ok(Selector::RecursiveWildcard {})
}
fn selector_recursive_key(reader: &mut Reader) -> Result<Selector, Error> {
try_literal("..", reader)?;
let k = key_name(reader)?;
@ -163,7 +228,14 @@ fn predicate(reader: &mut Reader) -> ParseResult<Predicate> {
// @.key>=value GreaterThanOrEqual(Key, Value)
literal("@.", reader)?; // assume key value for the time being
let key = key_name(reader)?;
let func = predicate_func(reader)?;
let state = reader.state.clone();
let func = match predicate_func(reader) {
Ok(f) => f,
Err(_) => {
reader.state = state;
PredicateFunc::KeyExist {}
}
};
Ok(Predicate { key, func })
}
@ -248,7 +320,7 @@ mod tests {
#[test]
pub fn test_query() {
let expected_query = Query {
selectors: vec![Selector::ArrayIndex(2)],
selectors: vec![Selector::ArrayIndex(vec![2])],
};
assert_eq!(query(&mut Reader::init("$[2]")).unwrap(), expected_query);
@ -265,7 +337,7 @@ mod tests {
selectors: vec![
Selector::NameChild("store".to_string()),
Selector::NameChild("book".to_string()),
Selector::ArrayIndex(0),
Selector::ArrayIndex(vec![0]),
Selector::NameChild("title".to_string()),
],
};
@ -281,7 +353,7 @@ mod tests {
let expected_query = Query {
selectors: vec![
Selector::RecursiveKey("book".to_string()),
Selector::ArrayIndex(2),
Selector::ArrayIndex(vec![2]),
],
};
assert_eq!(
@ -301,6 +373,17 @@ mod tests {
#[test]
pub fn test_selector_filter() {
// Filter exist value
let mut reader = Reader::init("[?(@.isbn)]");
assert_eq!(
selector(&mut reader).unwrap(),
Selector::Filter(Predicate {
key: "isbn".to_string(),
func: PredicateFunc::KeyExist {},
})
);
assert_eq!(reader.state.cursor, 11);
// Filter equal on string with single quotes
let mut reader = Reader::init("[?(@.key=='value')]");
assert_eq!(
@ -311,6 +394,19 @@ mod tests {
})
);
assert_eq!(reader.state.cursor, 19);
let mut reader = Reader::init("[?(@.price<10)]");
assert_eq!(
selector(&mut reader).unwrap(),
Selector::Filter(Predicate {
key: "price".to_string(),
func: PredicateFunc::LessThan(Number {
int: 10,
decimal: 0
}),
})
);
assert_eq!(reader.state.cursor, 15);
}
#[test]
@ -326,13 +422,26 @@ mod tests {
#[test]
pub fn test_selector_array_index() {
let mut reader = Reader::init("[2]");
assert_eq!(selector(&mut reader).unwrap(), Selector::ArrayIndex(2));
assert_eq!(
selector(&mut reader).unwrap(),
Selector::ArrayIndex(vec![2])
);
assert_eq!(reader.state.cursor, 3);
let mut reader = Reader::init("[0,1]");
assert_eq!(
selector(&mut reader).unwrap(),
Selector::ArrayIndex(vec![0, 1])
);
assert_eq!(reader.state.cursor, 5);
// you don't need to keep the exact string
// this is not part of the AST
let mut reader = Reader::init(".[2]");
assert_eq!(selector(&mut reader).unwrap(), Selector::ArrayIndex(2));
assert_eq!(
selector(&mut reader).unwrap(),
Selector::ArrayIndex(vec![2])
);
assert_eq!(reader.state.cursor, 4);
}
@ -349,6 +458,29 @@ mod tests {
assert_eq!(reader.state.cursor, 4);
}
#[test]
pub fn test_selector_array_slice() {
let mut reader = Reader::init("[-1:]");
assert_eq!(
selector(&mut reader).unwrap(),
Selector::ArraySlice(Slice {
start: Some(-1),
end: None
})
);
assert_eq!(reader.state.cursor, 5);
let mut reader = Reader::init("[:2]");
assert_eq!(
selector(&mut reader).unwrap(),
Selector::ArraySlice(Slice {
start: None,
end: Some(2)
})
);
assert_eq!(reader.state.cursor, 4);
}
#[test]
pub fn test_key_bracket_selector() {
let mut reader = Reader::init("['key']");
@ -392,6 +524,15 @@ mod tests {
#[test]
pub fn test_predicate() {
// Key exists
assert_eq!(
predicate(&mut Reader::init("@.isbn")).unwrap(),
Predicate {
key: "isbn".to_string(),
func: PredicateFunc::KeyExist {},
}
);
// Filter equal on string with single quotes
assert_eq!(
predicate(&mut Reader::init("@.key=='value'")).unwrap(),
@ -409,6 +550,18 @@ mod tests {
func: PredicateFunc::Equal(Number { int: 1, decimal: 0 }),
}
);
// Filter less than int
assert_eq!(
predicate(&mut Reader::init("@.price<10")).unwrap(),
Predicate {
key: "price".to_string(),
func: PredicateFunc::LessThan(Number {
int: 10,
decimal: 0
}),
}
);
}
#[test]

View File

@ -99,8 +99,41 @@ pub fn number(reader: &mut Reader) -> ParseResult<'static, Number> {
pub fn string_value(reader: &mut Reader) -> Result<String, Error> {
try_literal("'", reader)?;
let s = reader.read_while(|c| *c != '\'');
literal("'", reader)?;
let mut s = "".to_string();
loop {
match reader.read() {
None => {
return Err(Error {
pos: reader.state.pos.clone(),
recoverable: false,
inner: ParseError::Expecting {
value: String::from("'"),
},
})
}
Some('\'') => break,
Some('\\') => {
// only single quote can be escaped
match reader.read() {
Some('\'') => {
s.push('\'');
}
_ => {
return Err(Error {
pos: reader.state.pos.clone(),
recoverable: false,
inner: ParseError::Expecting {
value: String::from("'"),
},
})
}
}
}
Some(c) => {
s.push(c);
}
}
}
whitespace(reader);
Ok(s)
}
@ -108,7 +141,7 @@ pub fn string_value(reader: &mut Reader) -> Result<String, Error> {
pub fn key_name(reader: &mut Reader) -> Result<String, Error> {
// // test python or javascript
//// subset that can used for dot notation
let s = reader.read_while(|c| c.is_alphabetic() || *c == '-' || *c == '_');
let s = reader.read_while(|c| c.is_alphabetic() || *c == '_');
whitespace(reader);
Ok(s)
}
@ -377,6 +410,9 @@ mod tests {
let mut reader = Reader::init("'hello'");
assert_eq!(string_value(&mut reader).unwrap(), "hello".to_string());
let mut reader = Reader::init("'\\''");
assert_eq!(string_value(&mut reader).unwrap(), "'".to_string());
let mut reader = Reader::init("1");
let error = string_value(&mut reader).err().unwrap();
assert_eq!(

View File

@ -17,93 +17,360 @@
*/
extern crate hurl;
use hurl::jsonpath;
use serde_json::json;
use std::fs::read_to_string;
use serde_json::json;
fn bookstore_value() -> serde_json::Value {
let s = read_to_string("tests/bookstore.json").expect("could not read string from file");
serde_json::from_str(s.as_str()).expect("could not parse json file")
}
use hurl::jsonpath;
fn store_value() -> serde_json::Value {
serde_json::from_str(
r#"
{
"book": [
{
"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{
"category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
},
{
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
},
{
"category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
}
"#,
)
.unwrap()
}
fn test_ok(s: &str, value: serde_json::Value) -> Vec<serde_json::Value> {
return match jsonpath::parse(s) {
Ok(expr) => expr.eval(value),
Err(e) => panic!("{:?}", e),
};
fn book_value() -> serde_json::Value {
serde_json::from_str(
r#"
[
{
"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{
"category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
},
{
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
},
{
"category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
}
]
"#,
)
.unwrap()
}
fn bicycle_value() -> serde_json::Value {
serde_json::from_str(
r#"
{
"color": "red",
"price": 19.95
}
"#,
)
.unwrap()
}
fn book0_value() -> serde_json::Value {
json!( { "category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
})
}
fn book1_value() -> serde_json::Value {
json!( { "category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
})
}
fn book2_value() -> serde_json::Value {
json!( {
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
})
}
fn book3_value() -> serde_json::Value {
json!({ "category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
})
}
#[test]
fn test_bookstore_path() {
// examples from https://goessner.net/articles/JsonPath/
//the authors of all books in the store
let expr = jsonpath::parse("$.store.book[*].author").unwrap();
assert_eq!(
expr.eval(bookstore_value()),
vec![
json!("Nigel Rees"),
json!("Evelyn Waugh"),
json!("Herman Melville"),
json!("J. R. R. Tolkien")
]
);
// all authors
let expr = jsonpath::parse("$..author").unwrap();
assert_eq!(
expr.eval(bookstore_value()),
vec![
json!("Nigel Rees"),
json!("Evelyn Waugh"),
json!("Herman Melville"),
json!("J. R. R. Tolkien")
]
);
// all things in store, which are some books and a red bicycle.
let expr = jsonpath::parse("$.store.*").unwrap();
// Attention, there is no ordering on object keys with serde_json
// But you expect that order stays the same
// that's why bicycle and boot are inverted
assert_eq!(
expr.eval(bookstore_value()),
vec![bicycle_value(), book_value(),]
);
// the price of everything in the store.
let expr = jsonpath::parse("$.store..price").unwrap();
// Attention, there is no ordering on object keys with serde_json
// But you expect that order stays the same
assert_eq!(
expr.eval(bookstore_value()),
vec![
json!(19.95),
json!(8.95),
json!(12.99),
json!(8.99),
json!(22.99),
]
);
// the third book
let expr = jsonpath::parse("$..book[2]").unwrap();
assert_eq!(expr.eval(bookstore_value()), vec![book2_value()]);
// the last book in order
// The following expression is not supported
// (@.length-1)
// use python-like indexing instead
let expr = jsonpath::parse("$..book[-1:]").unwrap();
assert_eq!(expr.eval(bookstore_value()), vec![book3_value()]);
// the first two books
let expr = jsonpath::parse("$..book[0,1]").unwrap();
assert_eq!(
expr.eval(bookstore_value()),
vec![book0_value(), book1_value()]
);
let expr = jsonpath::parse("$..book[:2]").unwrap();
assert_eq!(
expr.eval(bookstore_value()),
vec![book0_value(), book1_value()]
);
// filter all books with isbn number
let expr = jsonpath::parse("$..book[?(@.isbn)]").unwrap();
assert_eq!(
expr.eval(bookstore_value()),
vec![book2_value(), book3_value(),]
);
// filter all books cheapier than 10
let expr = jsonpath::parse("$..book[?(@.price<10)]").unwrap();
assert_eq!(
expr.eval(bookstore_value()),
vec![book0_value(), book2_value(),]
);
// All members of JSON structure
let expr = jsonpath::parse("$..*").unwrap();
// Order is reproducible
// but does not keep same order of json input!
assert_eq!(
expr.eval(bookstore_value()),
vec![
store_value(),
bicycle_value(),
json!("red"),
json!(19.95),
book_value(),
book0_value(),
json!("Nigel Rees"),
json!("reference"),
json!(8.95),
json!("Sayings of the Century"),
book1_value(),
json!("Evelyn Waugh"),
json!("fiction"),
json!(12.99),
json!("Sword of Honour"),
book2_value(),
json!("Herman Melville"),
json!("fiction"),
json!("0-553-21311-3"),
json!(8.99),
json!("Moby Dick"),
book3_value(),
json!("J. R. R. Tolkien"),
json!("fiction"),
json!("0-395-19395-8"),
json!(22.99),
json!("The Lord of the Rings"),
]
);
}
#[test]
fn test_bookstore_additional() {
let no_result: Vec<serde_json::Value> = vec![];
let s = read_to_string("tests/bookstore.json").expect("could not read string from file");
let value: serde_json::Value =
serde_json::from_str(s.as_str()).expect("could not parse json file");
// $.store.book[*].author
assert_eq!(
test_ok("$.store.book[*].author", value.clone()),
vec![
json!("Nigel Rees"),
json!("Evelyn Waugh"),
json!("Herman Melville"),
json!("J. R. R. Tolkien")
]
);
// Find books more expensive than 100
let expr = jsonpath::parse("$.store.book[?(@.price>100)]").unwrap();
assert_eq!(expr.eval(bookstore_value()), no_result);
assert_eq!(
test_ok("$.store.book[0].title", value.clone()),
vec![json!("Sayings of the Century")]
);
assert_eq!(
test_ok("$.store.book.[0].title", value.clone()),
vec![json!("Sayings of the Century")]
);
assert_eq!(
test_ok("$.store.book[0].title", value.clone()),
vec![json!("Sayings of the Century")]
);
assert_eq!(
test_ok("$.store.book[?(@.price<10)].title", value.clone()),
vec![json!("Sayings of the Century"), json!("Moby Dick")]
);
assert_eq!(
test_ok("$.store.book[?(@.price < 10)].title", value.clone()),
vec![json!("Sayings of the Century"), json!("Moby Dick")]
);
assert_eq!(
test_ok("$..book[2]", value.clone()),
vec![json!({
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
})]
);
assert_eq!(
test_ok("$..author", value.clone()),
vec![
json!("Nigel Rees"),
json!("Evelyn Waugh"),
json!("Herman Melville"),
json!("J. R. R. Tolkien")
]
);
assert_eq!(
test_ok("$.store.book[?(@.price>100)]", value.clone()),
no_result
);
assert_eq!(
test_ok("$..book[?(@.category=='reference')].author", value),
vec![json!("Nigel Rees")]
);
// find all authors for reference book
let expr = jsonpath::parse("$..book[?(@.category=='reference')].author").unwrap();
assert_eq!(expr.eval(bookstore_value()), vec![json!("Nigel Rees")]);
}
#[test]
fn test_array() {
let array = json!([0, 1, 2, 3]);
assert_eq!(test_ok("$[2]", array), vec![json!(2)]);
let expr = jsonpath::parse("$[2]").unwrap();
assert_eq!(expr.eval(array), vec![json!(2)]);
let expr = jsonpath::parse("$[0].name").unwrap();
let array = json!([{"name": "Bob"},{"name": "Bill"}]);
assert_eq!(test_ok("$[0].name", array), vec![json!("Bob")]);
assert_eq!(expr.eval(array), vec![json!("Bob")]);
}
#[test]
fn test_key_access() {
let obj = json!({
"_": "underscore",
"-": "hyphen",
"*": "asterisk",
"'": "single_quote",
"\"": "double_quote",
"": "plane"
});
// Bracket notation
let expr = jsonpath::parse("$['-']").unwrap();
assert_eq!(expr.eval(obj.clone()), vec![json!("hyphen")]);
let expr = jsonpath::parse("$['_']").unwrap();
assert_eq!(expr.eval(obj.clone()), vec![json!("underscore")]);
let expr = jsonpath::parse("$['*']").unwrap();
assert_eq!(expr.eval(obj.clone()), vec![json!("asterisk")]);
let expr = jsonpath::parse("$['\\'']").unwrap();
assert_eq!(expr.eval(obj.clone()), vec![json!("single_quote")]);
let expr = jsonpath::parse("$['\"']").unwrap();
assert_eq!(expr.eval(obj.clone()), vec![json!("double_quote")]);
let expr = jsonpath::parse("$['✈']").unwrap();
assert_eq!(expr.eval(obj.clone()), vec![json!("plane")]);
// Dot notation
let expr = jsonpath::parse("$._").unwrap();
assert_eq!(expr.eval(obj.clone()), vec![json!("underscore")]);
// Asterisk
// return all elements
// There is no ordering in JSON keys
// You must compare with their string values sorted
let values = vec![
"asterisk",
"double_quote",
"hyphen",
"plane",
"single_quote",
"underscore",
];
let expr = jsonpath::parse("$.*").unwrap();
let results = expr.eval(obj.clone());
let mut results = results
.iter()
.map(|e| e.as_str().unwrap())
.collect::<Vec<&str>>();
results.sort_unstable();
assert_eq!(results, values);
let expr = jsonpath::parse("$[*]").unwrap();
let results = expr.eval(obj);
let mut results = results
.iter()
.map(|e| e.as_str().unwrap())
.collect::<Vec<&str>>();
results.sort_unstable();
assert_eq!(results, values);
}
#[test]
fn test_parsing_error() {
// not supported yet
assert!(jsonpath::parse("$..book[(@.length-1)]").is_err());
}