Skip to content

Commit

Permalink
POC API that avoids clone for consideration. (#44)
Browse files Browse the repository at this point in the history
* Add a POC API that avoids clone for consideration.

It looks like the underlying query implementation supports query without having to clone the input data, however the use of JsonPathFinder forces this to happen.

Added a POC API for querying without cloning that introduces a new enum `JsonPtr` for holding values. The reason this has to exist is that deref on JsonPathValue is not possible.
  • Loading branch information
BrynCooke authored Jul 22, 2023
1 parent 4283375 commit 7df5619
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 0 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,20 @@ fn test() {
}
```

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 structure

```rust
Expand Down
58 changes: 58 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ use crate::path::{json_path_instance, PathInstance};
use serde_json::Value;
use std::convert::TryInto;
use std::fmt::Debug;
use std::ops::Deref;
use std::str::FromStr;
use JsonPathValue::{NewValue, NoValue, Slice};

Expand Down Expand Up @@ -178,6 +179,42 @@ impl FromStr for JsonPathInst {
}
}

impl JsonPathInst {
pub fn find_slice<'a>(&'a self, value: &'a Value) -> Vec<JsonPtr<'a, Value>> {
json_path_instance(&self.inner, value)
.find(value.into())
.into_iter()
.filter(|v| v.has_value())
.map(|v| match v {
JsonPathValue::Slice(v) => JsonPtr::Slice(v),
JsonPathValue::NewValue(v) => JsonPtr::NewValue(v),
JsonPathValue::NoValue => unreachable!("has_value was already checked"),
})
.collect()
}
}

/// Json paths may return either pointers to the original json or new data. This custom pointer type allows us to handle both cases.
/// Unlike JsonPathValue, this type does not represent NoValue to allow the implementation of Deref.
pub enum JsonPtr<'a, Data> {
/// The slice of the initial json data
Slice(&'a Data),
/// The new data that was generated from the input data (like length operator)
NewValue(Data),
}

/// Allow deref from json pointer to value.
impl<'a> Deref for JsonPtr<'a, Value> {
type Target = Value;

fn deref(&self) -> &Self::Target {
match self {
JsonPtr::Slice(v) => v,
JsonPtr::NewValue(v) => v,
}
}
}

impl JsonPathQuery for Box<Value> {
fn path(self, query: &str) -> Result<Value, String> {
let p = JsonPathInst::from_str(query)?;
Expand Down Expand Up @@ -400,6 +437,7 @@ mod tests {
use crate::JsonPathValue::{NoValue, Slice};
use crate::{json_path_value, JsonPathFinder, JsonPathInst, JsonPathValue};
use serde_json::{json, Value};
use std::ops::Deref;
use std::str::FromStr;

fn test(json: &str, path: &str, expected: Vec<JsonPathValue<Value>>) {
Expand Down Expand Up @@ -1012,6 +1050,26 @@ mod tests {
assert_eq!(v, vec![NoValue]);
}

#[test]
fn no_clone_api_test() {
fn test_coercion(value: &Value) -> Value {
value.clone()
}

let json: Value = serde_json::from_str(template_json()).expect("to get json");
let query = JsonPathInst::from_str("$..book[?(@.author size 10)].title")
.expect("the path is correct");

let results = query.find_slice(&json);
let v = results.get(0).expect("to get value");

// V can be implicitly converted to &Value
test_coercion(v);

// To explicitly convert to &Value, use deref()
assert_eq!(v.deref(), &json!("Sayings of the Century"));
}

// #[test]
// fn no_value_len_field_test() {
// let json: Box<Value> =
Expand Down

0 comments on commit 7df5619

Please sign in to comment.