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

Keep track of traversed path and implement JsonPath::query_path_and_value #77

Closed
wants to merge 1 commit into from
Closed
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
30 changes: 28 additions & 2 deletions serde_json_path/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use serde::{de::Visitor, Deserialize, Serialize};
use serde_json::Value;
use serde_json_path_core::{
node::NodeList,
spec::query::{Query, Queryable},
spec::query::{Query, QueryResult, Queryable},
};

use crate::{parser::parse_query_main, ParseError};
Expand Down Expand Up @@ -73,7 +73,33 @@ impl JsonPath {
/// # }
/// ```
pub fn query<'b>(&self, value: &'b Value) -> NodeList<'b> {
self.0.query(value, value).into()
self.0
.query(value, value, vec![])
.into_iter()
.map(|(_path, value)| value)
.collect::<Vec<_>>()
.into()
}

/// Query a [`serde_json_path_core::spec::query::QueryResult`] with each
/// match having a path as [`Vec<String>`] and value as
/// [`serde_json::Value`]
///
/// # Example
/// ```rust
/// # use serde_json::json;
/// # use serde_json_path::JsonPath;
/// # fn main() -> Result<(), serde_json_path::ParseError> {
/// let path = JsonPath::parse("$.foo[::2]")?;
/// let value = json!({"foo": [1, 2, 3, 4]});
/// let result = path.query_path_and_value(&value);
/// assert_eq!(result[1].0, vec!["foo", "2"]);
/// assert_eq!(result[1].1, 3);
/// # Ok(())
/// # }
/// ```
pub fn query_path_and_value<'b>(&self, value: &'b Value) -> QueryResult<'b> {
self.0.query(value, value, vec![])
}
}

Expand Down
12 changes: 9 additions & 3 deletions serde_json_path_core/src/spec/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -581,11 +581,17 @@ impl FunctionExprArg {
fn evaluate<'a, 'b: 'a>(&'a self, current: &'b Value, root: &'b Value) -> JsonPathValue<'a> {
match self {
FunctionExprArg::Literal(lit) => lit.into(),
FunctionExprArg::SingularQuery(q) => match q.eval_query(current, root) {
Some(n) => JsonPathValue::Node(n),
FunctionExprArg::SingularQuery(q) => match q.eval_query(current, root, vec![]) {
Some(n) => JsonPathValue::Node(n.1),
None => JsonPathValue::Nothing,
},
FunctionExprArg::FilterQuery(q) => JsonPathValue::Nodes(q.query(current, root).into()),
FunctionExprArg::FilterQuery(q) => JsonPathValue::Nodes(
q.query(current, root, vec![])
.into_iter()
.map(|(_, nodes)| nodes)
.collect::<Vec<_>>()
.into(),
),
FunctionExprArg::LogicalExpr(l) => match l.test_filter(current, root) {
true => JsonPathValue::Logical(LogicalType::True),
false => JsonPathValue::Logical(LogicalType::False),
Expand Down
28 changes: 22 additions & 6 deletions serde_json_path_core/src/spec/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,21 @@ mod sealed {
impl Sealed for SingularQuery {}
}

/// Traversed path up to current node
pub type TraversedPath = Vec<String>;

/// A query result with each match having a path and value
pub type QueryResult<'a> = Vec<(Vec<String>, &'a Value)>;

/// A type that is query-able
pub trait Queryable: sealed::Sealed {
/// Query `self` using a current node, and the root node
fn query<'b>(&self, current: &'b Value, root: &'b Value) -> Vec<&'b Value>;
fn query<'b>(
&self,
current: &'b Value,
root: &'b Value,
traversed_path: TraversedPath,
) -> QueryResult<'b>;
}

/// Represents a JSONPath expression
Expand Down Expand Up @@ -83,15 +94,20 @@ pub enum QueryKind {

impl Queryable for Query {
#[cfg_attr(feature = "trace", tracing::instrument(name = "Main Query", level = "trace", parent = None, ret))]
fn query<'b>(&self, current: &'b Value, root: &'b Value) -> Vec<&'b Value> {
fn query<'b>(
&self,
current: &'b Value,
root: &'b Value,
traversed_path: TraversedPath,
) -> QueryResult<'b> {
let mut query = match self.kind {
QueryKind::Root => vec![root],
QueryKind::Current => vec![current],
QueryKind::Root => vec![(traversed_path.to_vec(), root)],
QueryKind::Current => vec![(traversed_path.to_vec(), current)],
};
for segment in &self.segments {
let mut new_query = Vec::new();
for q in &query {
new_query.append(&mut segment.query(q, root));
for (traversed_path, q) in &query {
new_query.append(&mut segment.query(q, root, traversed_path.clone()));
}
query = new_query;
}
Expand Down
49 changes: 35 additions & 14 deletions serde_json_path_core/src/spec/segment.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
//! Types representing segments in JSONPath
use serde_json::Value;

use super::{query::Queryable, selector::Selector};
use super::{
query::{QueryResult, Queryable, TraversedPath},
selector::Selector,
};

/// A segment of a JSONPath query
#[derive(Debug, PartialEq, Eq, Clone)]
Expand Down Expand Up @@ -48,25 +51,35 @@ pub enum QuerySegmentKind {

impl Queryable for QuerySegment {
#[cfg_attr(feature = "trace", tracing::instrument(name = "Query Path Segment", level = "trace", parent = None, ret))]
fn query<'b>(&self, current: &'b Value, root: &'b Value) -> Vec<&'b Value> {
let mut query = self.segment.query(current, root);
fn query<'b>(
&self,
current: &'b Value,
root: &'b Value,
traversed_path: TraversedPath,
) -> QueryResult<'b> {
let mut query = self.segment.query(current, root, traversed_path.clone());
if matches!(self.kind, QuerySegmentKind::Descendant) {
query.append(&mut descend(self, current, root));
query.append(&mut descend(self, current, root, traversed_path));
}
query
}
}

#[cfg_attr(feature = "trace", tracing::instrument(name = "Descend", level = "trace", parent = None, ret))]
fn descend<'b>(segment: &QuerySegment, current: &'b Value, root: &'b Value) -> Vec<&'b Value> {
fn descend<'b>(
segment: &QuerySegment,
current: &'b Value,
root: &'b Value,
traversed_path: TraversedPath,
) -> QueryResult<'b> {
let mut query = Vec::new();
if let Some(list) = current.as_array() {
for v in list {
query.append(&mut segment.query(v, root));
query.append(&mut segment.query(v, root, traversed_path.clone()));
}
} else if let Some(obj) = current.as_object() {
for (_, v) in obj {
query.append(&mut segment.query(v, root));
query.append(&mut segment.query(v, root, traversed_path.clone()));
}
}
query
Expand Down Expand Up @@ -145,29 +158,37 @@ impl std::fmt::Display for Segment {

impl Queryable for Segment {
#[cfg_attr(feature = "trace", tracing::instrument(name = "Query Segment", level = "trace", parent = None, ret))]
fn query<'b>(&self, current: &'b Value, root: &'b Value) -> Vec<&'b Value> {
fn query<'b>(
&self,
current: &'b Value,
root: &'b Value,
traversed_path: TraversedPath,
) -> QueryResult<'b> {
let mut query = Vec::new();
match self {
Segment::LongHand(selectors) => {
for selector in selectors {
query.append(&mut selector.query(current, root));
query.append(&mut selector.query(current, root, traversed_path.clone()));
}
}
Segment::DotName(key) => {
if let Some(obj) = current.as_object() {
if let Some(v) = obj.get(key) {
query.push(v);
query.push(([traversed_path.as_slice(), &[key.clone()]].concat(), v));
}
}
}
Segment::Wildcard => {
if let Some(list) = current.as_array() {
for v in list {
query.push(v);
for (index, v) in list.iter().enumerate() {
query.push((
[traversed_path.as_slice(), &[index.to_string()]].concat(),
v,
));
}
} else if let Some(obj) = current.as_object() {
for (_, v) in obj {
query.push(v);
for (key, v) in obj {
query.push(([traversed_path.as_slice(), &[key.clone()]].concat(), v));
}
}
}
Expand Down
65 changes: 46 additions & 19 deletions serde_json_path_core/src/spec/selector/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use serde_json::{Number, Value};

use crate::spec::{
functions::{FunctionExpr, JsonPathValue, Validated},
query::{Query, QueryKind, Queryable},
query::{Query, QueryKind, QueryResult, Queryable, TraversedPath},
segment::{QuerySegment, Segment},
};

Expand Down Expand Up @@ -55,8 +55,13 @@ impl std::fmt::Display for Filter {

impl Queryable for Filter {
#[cfg_attr(feature = "trace", tracing::instrument(name = "Query Filter", level = "trace", parent = None, ret))]
fn query<'b>(&self, current: &'b Value, root: &'b Value) -> Vec<&'b Value> {
if let Some(list) = current.as_array() {
fn query<'b>(
&self,
current: &'b Value,
root: &'b Value,
traversed_path: TraversedPath,
) -> QueryResult<'b> {
let value = if let Some(list) = current.as_array() {
list.iter()
.filter(|v| self.0.test_filter(v, root))
.collect()
Expand All @@ -67,7 +72,12 @@ impl Queryable for Filter {
.collect()
} else {
vec![]
}
};

value
.into_iter()
.map(|v| (traversed_path.to_vec(), v))
.collect()
}
}

Expand Down Expand Up @@ -197,7 +207,7 @@ impl std::fmt::Display for ExistExpr {
impl TestFilter for ExistExpr {
#[cfg_attr(feature = "trace", tracing::instrument(name = "Test Exists Expr", level = "trace", parent = None, ret))]
fn test_filter<'b>(&self, current: &'b Value, root: &'b Value) -> bool {
!self.0.query(current, root).is_empty()
!self.0.query(current, root, vec![]).is_empty()
}
}

Expand Down Expand Up @@ -393,8 +403,8 @@ impl Comparable {
) -> JsonPathValue<'a> {
match self {
Comparable::Literal(lit) => lit.into(),
Comparable::SingularQuery(sp) => match sp.eval_query(current, root) {
Some(v) => JsonPathValue::Node(v),
Comparable::SingularQuery(sp) => match sp.eval_query(current, root, vec![]) {
Some(v) => JsonPathValue::Node(v.1),
None => JsonPathValue::Nothing,
},
Comparable::FunctionExpr(expr) => expr.evaluate(current, root),
Expand Down Expand Up @@ -514,26 +524,38 @@ pub struct SingularQuery {
impl SingularQuery {
/// Evaluate the singular query
#[cfg_attr(feature = "trace", tracing::instrument(name = "SingularQuery::eval_query", level = "trace", parent = None, ret))]
pub fn eval_query<'b>(&self, current: &'b Value, root: &'b Value) -> Option<&'b Value> {
pub fn eval_query<'b>(
&self,
current: &'b Value,
root: &'b Value,
traversed_path: TraversedPath,
) -> Option<(Vec<String>, &'b Value)> {
let mut target = match self.kind {
SingularQueryKind::Absolute => root,
SingularQueryKind::Relative => current,
SingularQueryKind::Absolute => (traversed_path.to_vec(), root),
SingularQueryKind::Relative => (traversed_path.to_vec(), current),
};
for segment in &self.segments {
match segment {
SingularQuerySegment::Name(name) => {
if let Some(t) = target.as_object().and_then(|o| o.get(name.as_str())) {
target = t;
if let Some(t) = target.1.as_object().and_then(|o| o.get(name.as_str())) {
target = (
[traversed_path.as_slice(), &[name.as_str().to_owned()]].concat(),
t,
);
} else {
return None;
}
}
SingularQuerySegment::Index(index) => {
if let Some(t) = target
.as_array()
.and_then(|l| usize::try_from(index.0).ok().and_then(|i| l.get(i)))
{
target = t;
if let Some((index, t)) = target.1.as_array().and_then(|l| {
usize::try_from(index.0)
.ok()
.and_then(|i| l.get(i).map(|v| (i, v)))
}) {
target = (
[traversed_path.as_slice(), &[index.to_string()]].concat(),
t,
);
} else {
return None;
}
Expand All @@ -559,8 +581,13 @@ impl TryFrom<Query> for SingularQuery {
}

impl Queryable for SingularQuery {
fn query<'b>(&self, current: &'b Value, root: &'b Value) -> Vec<&'b Value> {
match self.eval_query(current, root) {
fn query<'b>(
&self,
current: &'b Value,
root: &'b Value,
traversed_path: TraversedPath,
) -> QueryResult<'b> {
match self.eval_query(current, root, traversed_path) {
Some(v) => vec![v],
None => vec![],
}
Expand Down
23 changes: 19 additions & 4 deletions serde_json_path_core/src/spec/selector/index.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Index selectors in JSONPath
use serde_json::Value;

use crate::spec::query::Queryable;
use crate::spec::query::{QueryResult, Queryable, TraversedPath};

/// For selecting array elements by their index
///
Expand All @@ -17,8 +17,13 @@ impl std::fmt::Display for Index {

impl Queryable for Index {
#[cfg_attr(feature = "trace", tracing::instrument(name = "Query Index", level = "trace", parent = None, ret))]
fn query<'b>(&self, current: &'b Value, _root: &'b Value) -> Vec<&'b Value> {
if let Some(list) = current.as_array() {
fn query<'b>(
&self,
current: &'b Value,
_root: &'b Value,
traversed_path: TraversedPath,
) -> QueryResult<'b> {
let values = if let Some(list) = current.as_array() {
if self.0 < 0 {
self.0
.checked_abs()
Expand All @@ -36,7 +41,17 @@ impl Queryable for Index {
}
} else {
vec![]
}
};

values
.into_iter()
.map(|v| {
(
[traversed_path.as_slice(), &[self.0.to_string()]].concat(),
v,
)
})
.collect()
}
}

Expand Down
Loading