Skip to content

Commit

Permalink
cleanup after 0.6.0 changes (#66)
Browse files Browse the repository at this point in the history
* mention the License via name, so that its shown on https://crates.io/crates/jsonpath-rust instead of "non-standart", update to rust 2021

* provide a minimal example code, fix clippy issues and harden CI to verify everything

* update README.md with most current usage.
try to keep it simple, and link to docs as they are actually tested in CI via `cargo test`
  • Loading branch information
xMAC94x authored Jun 11, 2024
1 parent 1d375e6 commit 3eb0149
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 169 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
profile: minimal
toolchain: stable
components: clippy
- run: cargo clippy --workspace --tests --all-features -- -D warnings
- run: cargo clippy --workspace --all-targets --all-features -- -D warnings

test:
runs-on: ubuntu-latest
Expand Down
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ name = "jsonpath-rust"
description = "The library provides the basic functionality to find the set of the data according to the filtering query."
version = "0.6.0"
authors = ["BorisZhguchev <[email protected]>"]
edition = "2018"
license-file = "LICENSE"
edition = "2021"
license = "MIT"
homepage = "https://github.com/besok/jsonpath-rust"
repository = "https://github.com/besok/jsonpath-rust"
readme = "README.md"
Expand Down
183 changes: 23 additions & 160 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ Given the json
| `$..book[?(@.author ~= '(?i)REES')]` | All books matching regex (ignore case) |
| `$..*` | Give me every thing |

### The library
## Library Usage

The library intends to provide the basic functionality for ability to find the slices of data using the syntax, saying
above. The dependency can be found as following:
Expand All @@ -251,180 +251,43 @@ To extract data there are two methods, provided on the `value`:
```rust
let v:JsonPathValue<Value> =...
v.to_data();
v.slice_or( & some_dafult_value)

v.slice_or(&some_dafault_value)
```

```rust
use jsonpath_rust::JsonPathFinder;
use serde_json::{json, Value, JsonPathValue};
### Find

fn main() {
let finder = JsonPathFinder::from_str(r#"{"first":{"second":[{"active":1},{"passive":1}]}}"#, "$.first.second[?(@.active)]").unwrap();
let slice_of_data: Vec<&Value> = finder.find_slice();
let js = json!({"active":1});
assert_eq!(slice_of_data, vec![JsonPathValue::Slice(&js,"$.first.second[0]".to_string())]);
}
```
there are 3 different functions to find data inside a `value`.
All take references, to increase reusability. Especially json parsing and jsonpath parsing can take significant time, compared to a simple find.

or with a separate instantiation:
The methods `find`, `find_as_path` and `find_slice` take the same inputs, but handle them differently depending on your usecase. They are further described in the [docs](https://docs.rs/jsonpath-rust/latest/jsonpath_rust/index.html#functions).

```rust
use serde_json::{json, Value};
use crate::jsonpath_rust::{JsonPathFinder, JsonPathQuery, JsonPathInst, JsonPathValue};
use jsonpath_rust::{JsonPathInst, JsonPathValue};
use serde_json::json;
use std::str::FromStr;

fn test() {
let json: Value = serde_json::from_str("{}").unwrap();
let v = json.path("$..book[?(@.author size 10)].title").unwrap();
assert_eq!(v, json!([]));

let json: Value = serde_json::from_str("{}").unwrap();
let path = &json.path("$..book[?(@.author size 10)].title").unwrap();

assert_eq!(path, &json!(["Sayings of the Century"]));

let json: Box<Value> = serde_json::from_str("{}").unwrap();
let path: Box<JsonPathInst> = Box::from(JsonPathInst::from_str("$..book[?(@.author size 10)].title").unwrap());
let finder = JsonPathFinder::new(json, path);

let v = finder.find_slice();
let js = json!("Sayings of the Century");
assert_eq!(v, vec![JsonPathValue::Slice(&js,"$.book[0].title".to_string())]);
}

```
In case, if there is no match `find_slice` will return `vec![NoValue]` and `find` return `json!(null)`

```rust
use jsonpath_rust::JsonPathFinder;
use serde_json::{json, Value, JsonPathValue};

fn main() {
let finder = JsonPathFinder::from_str(r#"{"first":{"second":[{"active":1},{"passive":1}]}}"#, "$.no_field").unwrap();
let res_js = finder.find();
assert_eq!(res_js, json!(null));
}
```

also, it will work with the instances of [[Value]] as well.

```rust
use serde_json::Value;
use crate::jsonpath_rust::{JsonPathFinder, JsonPathQuery, JsonPathInst};
use crate::path::{json_path_instance, PathInstance};

fn test(json: Box<Value>, path: &str) {
let path = JsonPathInst::from_str(path).unwrap();
JsonPathFinder::new(json, path)
}
```

also, the trait `JsonPathQuery` can be used:

```rust

use serde_json::{json, Value};
use jsonpath_rust::JsonPathQuery;

fn test() {
let json: Value = serde_json::from_str("{}").unwrap();
let v = json.path("$..book[?(@.author size 10)].title").unwrap();
assert_eq!(v, json!([]));

let json: Value = serde_json::from_str(template_json()).unwrap();
let path = &json.path("$..book[?(@.author size 10)].title").unwrap();

assert_eq!(path, &json!(["Sayings of the Century"]));
}
```

also, `JsonPathInst` can be used to query the data without cloning.
```rust
use serde_json::{json, Value};
use crate::jsonpath_rust::{JsonPathInst};

fn test() {
let json: Value = serde_json::from_str("{}").expect("to get json");
let query = JsonPathInst::from_str("$..book[?(@.author size 10)].title").unwrap();

// To convert to &Value, use deref()
assert_eq!(query.find_slice(&json).get(0).expect("to get value").deref(), &json!("Sayings of the Century"));
}
```

The library can return a path describing the value instead of the value itself.
To do that, the method `find_as_path` can be used:

```rust
use jsonpath_rust::JsonPathFinder;
use serde_json::{json, Value, JsonPathValue};

fn main() {
let finder = JsonPathFinder::from_str(r#"{"first":{"second":[{"active":1},{"passive":1}]}}"#, "$.first.second[?(@.active)]").unwrap();
let slice_of_data: Value = finder.find_as_path();
assert_eq!(slice_of_data, Value::Array(vec!["$.first.second[0]".to_string()]));
}
```
let data = json!({"first":{"second":[{"active":1},{"passive":1}]}});
let path = JsonPathInst::from_str("$.first.second[?(@.active)]").unwrap();
let slice_of_data = jsonpath_rust::find_slice(&path, &data);

or it can be taken from the `JsonPathValue` instance:
```rust
use serde_json::{json, Value};
use crate::jsonpath_rust::{JsonPathFinder, JsonPathQuery, JsonPathInst, JsonPathValue};
use std::str::FromStr;

fn test() {
let json: Box<Value> = serde_json::from_str("{}").unwrap();
let path: Box<JsonPathInst> = Box::from(JsonPathInst::from_str("$..book[?(@.author size 10)].title").unwrap());
let finder = JsonPathFinder::new(json, path);
let expected_value = json!({"active":1});
let expected_path = "$.['first'].['second'][0]".to_string();

let v = finder.find_slice();
let js = json!("Sayings of the Century");

// Slice has a path of its value as well
assert_eq!(v, vec![JsonPathValue::Slice(&js,"$.book[0].title".to_string())]);
assert_eq!(
slice_of_data,
vec![JsonPathValue::Slice(&expected_value, expected_path)]
);
}
```

** If the value has been modified during the search, there is no way to find a path of a new value.
It can happen if we try to find a length() of array, for in stance.**

### The structure

The internal structure of the `JsonPath` can be found here:
https://docs.rs/jsonpath-rust/latest/jsonpath_rust/parser/model/enum.JsonPath.html

## The structure

```rust
pub enum JsonPath {
Root,
// <- $
Field(String),
// <- field of the object
Chain(Vec<JsonPath>),
// <- the whole jsonpath
Descent(String),
// <- '..'
Index(JsonPathIndex),
// <- the set of indexes represented by the next structure [[JsonPathIndex]]
Current(Box<JsonPath>),
// <- @
Wildcard,
// <- *
Empty, // the structure to avoid inconsistency
}

pub enum JsonPathIndex {
Single(usize),
// <- [1]
UnionIndex(Vec<f64>),
// <- [1,2,3]
UnionKeys(Vec<String>),
// <- ['key_1','key_2']
Slice(i32, i32, usize),
// [0:10:1]
Filter(Operand, FilterSign, Operand), // <- [?(operand sign operand)]
}

```
The internal structure of the `JsonPathIndex` can be found here:
https://docs.rs/jsonpath-rust/latest/jsonpath_rust/parser/model/enum.JsonPathIndex.html

## How to contribute

Expand All @@ -434,4 +297,4 @@ TBD
- update files
- commit them
- add tag `git tag -a v<Version> -m "message"`
- git push origin <tag_name>
- git push origin <tag_name>
4 changes: 2 additions & 2 deletions benches/equal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ struct SearchData {
path: JsonPathInst,
}

const PATH: &'static str = "$.[?(@.author == 'abcd(Rees)')]";
const PATH: &str = "$.[?(@.author == 'abcd(Rees)')]";

fn equal_perf_test_with_reuse(cfg: &SearchData) {
let _v = jsonpath_rust::find(&cfg.path, &cfg.json);
Expand All @@ -33,7 +33,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
b.iter(|| equal_perf_test_with_reuse(&data))
});
c.bench_function("equal bench without reuse", |b| {
b.iter(|| equal_perf_test_without_reuse())
b.iter(equal_perf_test_without_reuse)
});
}

Expand Down
6 changes: 3 additions & 3 deletions benches/regex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ struct SearchData {
path: JsonPathInst,
}

const PATH: &'static str = "$.[?(@.author ~= '.*(?i)d\\(Rees\\)')]";
const PATH: &str = "$.[?(@.author ~= '.*(?i)d\\(Rees\\)')]";

fn regex_perf_test_with_reuse(cfg: &SearchData) {
let _v = jsonpath_rust::find(&cfg.path, &cfg.json);
Expand Down Expand Up @@ -37,10 +37,10 @@ pub fn criterion_benchmark(c: &mut Criterion) {
b.iter(|| regex_perf_test_with_reuse(&data))
});
c.bench_function("regex bench without reuse", |b| {
b.iter(|| regex_perf_test_without_reuse())
b.iter(regex_perf_test_without_reuse)
});
c.bench_function("JsonPathInst generation", |b| {
b.iter(|| json_path_inst_compiling())
b.iter(json_path_inst_compiling)
});
}

Expand Down
13 changes: 13 additions & 0 deletions examples/hello-world.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use jsonpath_rust::JsonPathInst;
use serde_json::json;
use std::str::FromStr;

fn main() {
let data = json!({
"Hello":"World",
"Good":"Bye",
});
let path = JsonPathInst::from_str("$.Hello").unwrap();
let search_result = jsonpath_rust::find(&path, &data);
println!("Hello, {}", search_result);
}
56 changes: 55 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,27 @@ impl<'a, Data> JsonPathValue<'a, Data> {

/// finds a slice of data in the set json.
/// The result is a vector of references to the incoming structure.
///
/// In case, if there is no match `find_slice` will return `vec![NoValue]`.
///
/// ## Example
/// ```rust
/// use jsonpath_rust::{JsonPathInst, JsonPathValue};
/// use serde_json::json;
/// # use std::str::FromStr;
///
/// let data = json!({"first":{"second":[{"active":1},{"passive":1}]}});
/// let path = JsonPathInst::from_str("$.first.second[?(@.active)]").unwrap();
/// let slice_of_data = jsonpath_rust::find_slice(&path, &data);
///
/// let expected_value = json!({"active":1});
/// let expected_path = "$.['first'].['second'][0]".to_string();
///
/// assert_eq!(
/// slice_of_data,
/// vec![JsonPathValue::Slice(&expected_value, expected_path)]
/// );
/// ```
pub fn find_slice<'a>(path: &'a JsonPathInst, json: &'a Value) -> Vec<JsonPathValue<'a, Value>> {
let instance = json_path_instance(&path.inner, json);
let res = instance.find(JsonPathValue::from_root(json));
Expand All @@ -411,6 +432,21 @@ pub fn find_slice<'a>(path: &'a JsonPathInst, json: &'a Value) -> Vec<JsonPathVa

/// finds a slice of data and wrap it with Value::Array by cloning the data.
/// Returns either an array of elements or Json::Null if the match is incorrect.
///
/// In case, if there is no match `find` will return `json!(null)`.
///
/// ## Example
/// ```rust
/// use jsonpath_rust::{JsonPathInst, JsonPathValue};
/// use serde_json::{Value, json};
/// # use std::str::FromStr;
///
/// let data = json!({"first":{"second":[{"active":1},{"passive":1}]}});
/// let path = JsonPathInst::from_str("$.first.second[?(@.active)]").unwrap();
/// let cloned_data = jsonpath_rust::find(&path, &data);
///
/// assert_eq!(cloned_data, Value::Array(vec![json!({"active":1})]));
/// ```
pub fn find(path: &JsonPathInst, json: &Value) -> Value {
let slice = find_slice(path, json);
if !slice.is_empty() {
Expand All @@ -429,8 +465,26 @@ pub fn find(path: &JsonPathInst, json: &Value) -> Value {
Value::Array(vec![])
}
}
/// finds a path of the values.

/// finds a path describing the value, instead of the value itself.
/// If the values has been obtained by moving the data out of the initial json the path is absent.
///
/// ** If the value has been modified during the search, there is no way to find a path of a new value.
/// It can happen if we try to find a length() of array, for in stance.**
///
/// ## Example
/// ```rust
/// use jsonpath_rust::{JsonPathInst, JsonPathValue};
/// use serde_json::{Value, json};
/// # use std::str::FromStr;
///
/// let data = json!({"first":{"second":[{"active":1},{"passive":1}]}});
/// let path = JsonPathInst::from_str("$.first.second[?(@.active)]").unwrap();
/// let slice_of_data: Value = jsonpath_rust::find_as_path(&path, &data);
///
/// let expected_path = "$.['first'].['second'][0]".to_string();
/// assert_eq!(slice_of_data, Value::Array(vec![Value::String(expected_path)]));
/// ```
pub fn find_as_path(path: &JsonPathInst, json: &Value) -> Value {
Value::Array(
find_slice(path, json)
Expand Down
2 changes: 2 additions & 0 deletions src/parser/parser.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![allow(clippy::empty_docs)]

use crate::parser::errors::JsonPathParserError::ParserError;
use crate::parser::errors::{parser_err, JsonPathParserError};
use crate::parser::model::FilterExpression::{And, Not, Or};
Expand Down

0 comments on commit 3eb0149

Please sign in to comment.