Skip to content

Commit 0a69c56

Browse files
authored
add ref_mut (#80)
* add code * fix clippy * opt imp * correct docs
1 parent 0977b0c commit 0a69c56

File tree

7 files changed

+296
-55
lines changed

7 files changed

+296
-55
lines changed

CHANGELOG.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,6 @@
5757
- **`0.7.2`**
5858
- add JsonLike trait
5959
- **`0.7.3`**
60-
- make some methods public
60+
- make some methods public
61+
- **`0.7.5`**
62+
- add reference and reference_mut methods

Cargo.toml

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "jsonpath-rust"
33
description = "The library provides the basic functionality to find the set of the data according to the filtering query."
4-
version = "0.7.3"
4+
version = "0.7.5"
55
authors = ["BorisZhguchev <[email protected]>"]
66
edition = "2021"
77
license = "MIT"
@@ -16,10 +16,9 @@ serde_json = "1.0"
1616
regex = "1"
1717
pest = "2.0"
1818
pest_derive = "2.0"
19-
thiserror = "1.0.50"
19+
thiserror = "2.0.9"
2020

2121
[dev-dependencies]
22-
lazy_static = "1.0"
2322
criterion = "0.5.1"
2423

2524
[[bench]]

README.md

+55
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,61 @@ https://docs.rs/jsonpath-rust/latest/jsonpath_rust/parser/model/enum.JsonPathInd
298298
The library provides a trait `JsonLike` that can be implemented for any type.
299299
This allows you to use the `JsonPath` methods on your own types.
300300

301+
### Update the JsonLike structure by path
302+
303+
The library does not provide the functionality to update the json structure in the query itself.
304+
Instead, the library provides the ability to update the json structure by the path.
305+
Thus, the user needs to find a path for the `JsonLike` structure and update it manually.
306+
307+
There are two methods in the `JsonLike` trait:
308+
309+
- `reference_mut` - returns a mutable reference to the element by the path
310+
- `reference` - returns a reference to the element by the path
311+
They accept a `JsonPath` instance and return a `Option<&mut Value>` or `Option<&Value>` respectively.
312+
The path is supported with the limited elements namely only the elements with the direct access:
313+
- root
314+
- field
315+
- index
316+
The path can be obtained manually or `find_as_path` method can be used.
317+
318+
```rust
319+
#[test]
320+
fn update_by_path_test() -> Result<(), JsonPathParserError> {
321+
let mut json = json!([
322+
{"verb": "RUN","distance":[1]},
323+
{"verb": "TEST"},
324+
{"verb": "DO NOT RUN"}
325+
]);
326+
327+
let path: Box<JsonPath> = Box::from(JsonPath::try_from("$.[?(@.verb == 'RUN')]")?);
328+
let elem = path
329+
.find_as_path(&json)
330+
.get(0)
331+
.cloned()
332+
.ok_or(JsonPathParserError::InvalidJsonPath("".to_string()))?;
333+
334+
if let Some(v) = json
335+
.reference_mut(elem)?
336+
.and_then(|v| v.as_object_mut())
337+
.and_then(|v| v.get_mut("distance"))
338+
.and_then(|v| v.as_array_mut())
339+
{
340+
v.push(json!(2))
341+
}
342+
343+
assert_eq!(
344+
json,
345+
json!([
346+
{"verb": "RUN","distance":[1,2]},
347+
{"verb": "TEST"},
348+
{"verb": "DO NOT RUN"}
349+
])
350+
);
351+
352+
Ok(())
353+
}
354+
```
355+
301356
## How to contribute
302357

303358
TBD

src/jsonpath.rs

+47-28
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
use crate::path::json_path_instance;
22
use crate::path::JsonLike;
3-
use crate::JsonPath;
43
use crate::JsonPathValue;
5-
use crate::JsonPathValue::NoValue;
64
use crate::JsonPtr;
5+
use crate::{JsonPath, JsonPathStr};
76

87
impl<T> JsonPath<T>
98
where
@@ -39,7 +38,7 @@ where
3938
let has_v: Vec<JsonPathValue<'_, T>> = res.into_iter().filter(|v| v.has_value()).collect();
4039

4140
if has_v.is_empty() {
42-
vec![NoValue]
41+
vec![JsonPathValue::NoValue]
4342
} else {
4443
has_v
4544
}
@@ -105,33 +104,31 @@ where
105104
///
106105
/// ## Example
107106
/// ```rust
108-
/// use jsonpath_rust::{JsonPath, JsonPathValue};
107+
/// use jsonpath_rust::{JsonPathStr, JsonPath, JsonPathValue};
109108
/// use serde_json::{Value, json};
110109
/// # use std::str::FromStr;
111110
///
112111
/// let data = json!({"first":{"second":[{"active":1},{"passive":1}]}});
113112
/// let path = JsonPath::try_from("$.first.second[?(@.active)]").unwrap();
114-
/// let slice_of_data: Value = path.find_as_path(&data);
113+
/// let slice_of_data: Vec<JsonPathStr> = path.find_as_path(&data);
115114
///
116115
/// let expected_path = "$.['first'].['second'][0]".to_string();
117-
/// assert_eq!(slice_of_data, Value::Array(vec![Value::String(expected_path)]));
116+
/// assert_eq!(slice_of_data, vec![expected_path]);
118117
/// ```
119-
pub fn find_as_path(&self, json: &T) -> T {
120-
T::array(
121-
self.find_slice(json)
122-
.into_iter()
123-
.flat_map(|v| v.to_path())
124-
.map(|v| v.into())
125-
.collect(),
126-
)
118+
pub fn find_as_path(&self, json: &T) -> Vec<JsonPathStr> {
119+
self.find_slice(json)
120+
.into_iter()
121+
.flat_map(|v| v.to_path())
122+
.collect()
127123
}
128124
}
129125

130126
#[cfg(test)]
131127
mod tests {
128+
use crate::path::JsonLike;
132129
use crate::JsonPathQuery;
133130
use crate::JsonPathValue::{NoValue, Slice};
134-
use crate::{jp_v, JsonPath, JsonPathValue};
131+
use crate::{jp_v, JsonPath, JsonPathParserError, JsonPathValue};
135132
use serde_json::{json, Value};
136133
use std::ops::Deref;
137134

@@ -901,17 +898,39 @@ mod tests {
901898
);
902899
}
903900

904-
// #[test]
905-
// fn no_value_len_field_test() {
906-
// let json: Box<Value> =
907-
// Box::new(json!([{"verb": "TEST","a":[1,2,3]},{"verb": "TEST","a":[1,2,3]},{"verb": "TEST"}, {"verb": "RUN"}]));
908-
// let path: Box<JsonPath> = Box::from(
909-
// JsonPath::try_from("$.[?(@.verb == 'TEST')].a.length()")
910-
// .expect("the path is correct"),
911-
// );
912-
// let finder = JsonPathFinder::new(json, path);
913-
//
914-
// let v = finder.find_slice();
915-
// assert_eq!(v, vec![NewValue(json!(3))]);
916-
// }
901+
#[test]
902+
fn update_by_path_test() -> Result<(), JsonPathParserError> {
903+
let mut json = json!([
904+
{"verb": "RUN","distance":[1]},
905+
{"verb": "TEST"},
906+
{"verb": "DO NOT RUN"}
907+
]);
908+
909+
let path: Box<JsonPath> = Box::from(JsonPath::try_from("$.[?(@.verb == 'RUN')]")?);
910+
let elem = path
911+
.find_as_path(&json)
912+
.first()
913+
.cloned()
914+
.ok_or(JsonPathParserError::InvalidJsonPath("".to_string()))?;
915+
916+
if let Some(v) = json
917+
.reference_mut(elem)?
918+
.and_then(|v| v.as_object_mut())
919+
.and_then(|v| v.get_mut("distance"))
920+
.and_then(|v| v.as_array_mut())
921+
{
922+
v.push(json!(2))
923+
}
924+
925+
assert_eq!(
926+
json,
927+
json!([
928+
{"verb": "RUN","distance":[1,2]},
929+
{"verb": "TEST"},
930+
{"verb": "DO NOT RUN"}
931+
])
932+
);
933+
934+
Ok(())
935+
}
917936
}

src/lib.rs

+8-7
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ pub trait JsonPathQuery {
166166

167167
/// Json paths may return either pointers to the original json or new data. This custom pointer type allows us to handle both cases.
168168
/// Unlike JsonPathValue, this type does not represent NoValue to allow the implementation of Deref.
169+
#[derive(Debug, PartialEq, Clone)]
169170
pub enum JsonPtr<'a, Data> {
170171
/// The slice of the initial json data
171172
Slice(&'a Data),
@@ -261,7 +262,7 @@ macro_rules! jp_v {
261262
}
262263

263264
/// Represents the path of the found json data
264-
type JsPathStr = String;
265+
pub type JsonPathStr = String;
265266

266267
pub fn jsp_idx(prefix: &str, idx: usize) -> String {
267268
format!("{}[{}]", prefix, idx)
@@ -275,7 +276,7 @@ pub fn jsp_obj(prefix: &str, key: &str) -> String {
275276
#[derive(Debug, PartialEq, Clone)]
276277
pub enum JsonPathValue<'a, Data> {
277278
/// The slice of the initial json data
278-
Slice(&'a Data, JsPathStr),
279+
Slice(&'a Data, JsonPathStr),
279280
/// The new data that was generated from the input data (like length operator)
280281
NewValue(Data),
281282
/// The absent value that indicates the input data is not matched to the given json path (like the absent fields)
@@ -293,7 +294,7 @@ impl<'a, Data: Clone + Debug + Default> JsonPathValue<'a, Data> {
293294
}
294295

295296
/// Transforms given value into path
296-
pub fn to_path(self) -> Option<JsPathStr> {
297+
pub fn to_path(self) -> Option<JsonPathStr> {
297298
match self {
298299
Slice(_, path) => Some(path),
299300
_ => None,
@@ -313,15 +314,15 @@ impl<'a, Data> JsonPathValue<'a, Data> {
313314
!input.is_empty() && input.iter().filter(|v| v.has_value()).count() == 0
314315
}
315316

316-
pub fn map_vec(data: Vec<(&'a Data, JsPathStr)>) -> Vec<JsonPathValue<'a, Data>> {
317+
pub fn map_vec(data: Vec<(&'a Data, JsonPathStr)>) -> Vec<JsonPathValue<'a, Data>> {
317318
data.into_iter()
318319
.map(|(data, pref)| Slice(data, pref))
319320
.collect()
320321
}
321322

322323
pub fn map_slice<F>(self, mapper: F) -> Vec<JsonPathValue<'a, Data>>
323324
where
324-
F: FnOnce(&'a Data, JsPathStr) -> Vec<(&'a Data, JsPathStr)>,
325+
F: FnOnce(&'a Data, JsonPathStr) -> Vec<(&'a Data, JsonPathStr)>,
325326
{
326327
match self {
327328
Slice(r, pref) => mapper(r, pref)
@@ -336,7 +337,7 @@ impl<'a, Data> JsonPathValue<'a, Data> {
336337

337338
pub fn flat_map_slice<F>(self, mapper: F) -> Vec<JsonPathValue<'a, Data>>
338339
where
339-
F: FnOnce(&'a Data, JsPathStr) -> Vec<JsonPathValue<'a, Data>>,
340+
F: FnOnce(&'a Data, JsonPathStr) -> Vec<JsonPathValue<'a, Data>>,
340341
{
341342
match self {
342343
Slice(r, pref) => mapper(r, pref),
@@ -357,7 +358,7 @@ impl<'a, Data> JsonPathValue<'a, Data> {
357358
})
358359
.collect()
359360
}
360-
pub fn vec_as_pair(input: Vec<JsonPathValue<'a, Data>>) -> Vec<(&'a Data, JsPathStr)> {
361+
pub fn vec_as_pair(input: Vec<JsonPathValue<'a, Data>>) -> Vec<(&'a Data, JsonPathStr)> {
361362
input
362363
.into_iter()
363364
.filter_map(|v| match v {

src/parser/errors.rs

+2
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,6 @@ pub enum JsonPathParserError {
2424
InvalidTopLevelRule(Rule),
2525
#[error("Failed to get inner pairs for {0}")]
2626
EmptyInner(String),
27+
#[error("Invalid json path: {0}")]
28+
InvalidJsonPath(String),
2729
}

0 commit comments

Comments
 (0)