Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cleanup after 0.6.0 changes #66

Merged
merged 3 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 <zhguchev@gmail.com>"]
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
Loading