Skip to content

Commit

Permalink
feat: working implementation of normalized paths
Browse files Browse the repository at this point in the history
This provides a complete working implementation of the NormalizedPath type, and provides a method to query for it along with nodes on JsonPath.

A fair bit of work is still needed to get the API right, but committing here to have it in a working state.

An unused implementation of Queryable on SingularQuery was removed.
  • Loading branch information
hiltontj committed Jan 27, 2024
1 parent c01a942 commit cca17b4
Show file tree
Hide file tree
Showing 9 changed files with 222 additions and 96 deletions.
21 changes: 20 additions & 1 deletion serde_json_path/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ use serde::{de::Visitor, Deserialize, Serialize};
use serde_json::Value;
use serde_json_path_core::{
node::NodeList,
spec::query::{Query, Queryable},
spec::{
path::NormalizedPath,
query::{Query, Queryable},
},
};

use crate::{parser::parse_query_main, ParseError};
Expand Down Expand Up @@ -75,6 +78,10 @@ impl JsonPath {
pub fn query<'b>(&self, value: &'b Value) -> NodeList<'b> {
self.0.query(value, value).into()
}

pub fn query_paths<'b>(&self, value: &'b Value) -> Vec<(NormalizedPath<'b>, &'b Value)> {
self.0.query_paths_init(value, value)
}
}

impl FromStr for JsonPath {
Expand Down Expand Up @@ -153,4 +160,16 @@ mod tests {
.expect("round trip");
assert_eq!(p1, p2);
}

#[test]
fn norm_paths() {
let j = json!({"foo": {
"bar": [1, 2, 3]
}});
let p = JsonPath::parse("$.foo.bar.*").unwrap();
let r = p.query_paths(&j);
for (np, _) in r {
println!("{pointer}", pointer = np.as_json_pointer());
}
}
}
30 changes: 12 additions & 18 deletions serde_json_path_core/src/spec/path.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
use std::ops::{Deref, DerefMut};

#[derive(Clone)]
#[derive(Debug, Clone, Default)]
pub struct NormalizedPath<'a>(Vec<PathElement<'a>>);

Check failure on line 2 in serde_json_path_core/src/spec/path.rs

View workflow job for this annotation

GitHub Actions / Clippy

missing documentation for a struct

Check failure on line 2 in serde_json_path_core/src/spec/path.rs

View workflow job for this annotation

GitHub Actions / Test

missing documentation for a struct

Check warning on line 2 in serde_json_path_core/src/spec/path.rs

View workflow job for this annotation

GitHub Actions / Docs

missing documentation for a struct

Check failure on line 2 in serde_json_path_core/src/spec/path.rs

View workflow job for this annotation

GitHub Actions / Docs

missing documentation for a struct

impl<'a> NormalizedPath<'a> {
pub(crate) fn push<T: Into<PathElement<'a>>>(&mut self, elem: T) {
self.0.push(elem.into())
}

pub(crate) fn clone_and_push<T: Into<PathElement<'a>>>(&self, elem: T) -> Self {
let mut new_path = self.clone();
new_path.push(elem.into());
new_path
}

pub fn as_json_pointer(&self) -> String {

Check failure on line 15 in serde_json_path_core/src/spec/path.rs

View workflow job for this annotation

GitHub Actions / Clippy

missing documentation for a method

Check failure on line 15 in serde_json_path_core/src/spec/path.rs

View workflow job for this annotation

GitHub Actions / Test

missing documentation for a method

Check warning on line 15 in serde_json_path_core/src/spec/path.rs

View workflow job for this annotation

GitHub Actions / Docs

missing documentation for a method

Check failure on line 15 in serde_json_path_core/src/spec/path.rs

View workflow job for this annotation

GitHub Actions / Docs

missing documentation for a method
self.0
.iter()
Expand All @@ -16,21 +24,7 @@ impl<'a> NormalizedPath<'a> {
}
}

impl<'a> Deref for NormalizedPath<'a> {
type Target = Vec<PathElement<'a>>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl<'a> DerefMut for NormalizedPath<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

#[derive(Clone, Copy)]
#[derive(Debug, Clone, Copy)]
pub enum PathElement<'a> {

Check failure on line 28 in serde_json_path_core/src/spec/path.rs

View workflow job for this annotation

GitHub Actions / Clippy

missing documentation for an enum

Check failure on line 28 in serde_json_path_core/src/spec/path.rs

View workflow job for this annotation

GitHub Actions / Test

missing documentation for an enum

Check warning on line 28 in serde_json_path_core/src/spec/path.rs

View workflow job for this annotation

GitHub Actions / Docs

missing documentation for an enum

Check failure on line 28 in serde_json_path_core/src/spec/path.rs

View workflow job for this annotation

GitHub Actions / Docs

missing documentation for an enum
Name(&'a str),

Check failure on line 29 in serde_json_path_core/src/spec/path.rs

View workflow job for this annotation

GitHub Actions / Clippy

missing documentation for a variant

Check failure on line 29 in serde_json_path_core/src/spec/path.rs

View workflow job for this annotation

GitHub Actions / Test

missing documentation for a variant

Check warning on line 29 in serde_json_path_core/src/spec/path.rs

View workflow job for this annotation

GitHub Actions / Docs

missing documentation for a variant

Check failure on line 29 in serde_json_path_core/src/spec/path.rs

View workflow job for this annotation

GitHub Actions / Docs

missing documentation for a variant
Index(usize),

Check failure on line 30 in serde_json_path_core/src/spec/path.rs

View workflow job for this annotation

GitHub Actions / Clippy

missing documentation for a variant

Check failure on line 30 in serde_json_path_core/src/spec/path.rs

View workflow job for this annotation

GitHub Actions / Test

missing documentation for a variant

Check warning on line 30 in serde_json_path_core/src/spec/path.rs

View workflow job for this annotation

GitHub Actions / Docs

missing documentation for a variant

Check failure on line 30 in serde_json_path_core/src/spec/path.rs

View workflow job for this annotation

GitHub Actions / Docs

missing documentation for a variant
Expand Down
30 changes: 24 additions & 6 deletions serde_json_path_core/src/spec/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,16 @@ pub trait Queryable: sealed::Sealed {
fn query_paths<'b>(

Check failure on line 36 in serde_json_path_core/src/spec/query.rs

View workflow job for this annotation

GitHub Actions / Clippy

missing documentation for a method

Check failure on line 36 in serde_json_path_core/src/spec/query.rs

View workflow job for this annotation

GitHub Actions / Test

missing documentation for a method

Check warning on line 36 in serde_json_path_core/src/spec/query.rs

View workflow job for this annotation

GitHub Actions / Docs

missing documentation for a method

Check failure on line 36 in serde_json_path_core/src/spec/query.rs

View workflow job for this annotation

GitHub Actions / Docs

missing documentation for a method
&self,
current: &'b Value,
_root: &'b Value,
root: &'b Value,
parent: NormalizedPath<'b>,
) -> Vec<NormalizedPath<'b>>;
) -> Vec<(NormalizedPath<'b>, &'b Value)>;
fn query_paths_init<'b>(

Check failure on line 42 in serde_json_path_core/src/spec/query.rs

View workflow job for this annotation

GitHub Actions / Clippy

missing documentation for a method

Check failure on line 42 in serde_json_path_core/src/spec/query.rs

View workflow job for this annotation

GitHub Actions / Test

missing documentation for a method

Check warning on line 42 in serde_json_path_core/src/spec/query.rs

View workflow job for this annotation

GitHub Actions / Docs

missing documentation for a method

Check failure on line 42 in serde_json_path_core/src/spec/query.rs

View workflow job for this annotation

GitHub Actions / Docs

missing documentation for a method
&self,
current: &'b Value,
root: &'b Value,
) -> Vec<(NormalizedPath<'b>, &'b Value)> {
self.query_paths(current, root, Default::default())
}
}

/// Represents a JSONPath expression
Expand Down Expand Up @@ -107,9 +114,20 @@ impl Queryable for Query {
fn query_paths<'b>(
&self,
current: &'b Value,
_root: &'b Value,
parent: NormalizedPath<'b>,
) -> Vec<NormalizedPath<'b>> {
todo!()
root: &'b Value,
_parent: NormalizedPath<'b>,
) -> Vec<(NormalizedPath<'b>, &'b Value)> {
let mut result: Vec<(NormalizedPath<'b>, &Value)> = match self.kind {
QueryKind::Root => vec![(Default::default(), root)],
QueryKind::Current => vec![(Default::default(), current)],
};
for s in &self.segments {
let mut r = vec![];
for (ref np, v) in result {
r.append(&mut s.query_paths(v, root, np.clone()));
}
result = r;
}
result
}
}
68 changes: 59 additions & 9 deletions serde_json_path_core/src/spec/segment.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Types representing segments in JSONPath
use serde_json::Value;

use super::{query::Queryable, selector::Selector};
use super::{path::NormalizedPath, query::Queryable, selector::Selector};

/// A segment of a JSONPath query
#[derive(Debug, PartialEq, Eq, Clone)]
Expand Down Expand Up @@ -59,10 +59,16 @@ impl Queryable for QuerySegment {
fn query_paths<'b>(
&self,
current: &'b Value,
_root: &'b Value,
parent: super::path::NormalizedPath<'b>,
) -> Vec<super::path::NormalizedPath<'b>> {
todo!()
root: &'b Value,
parent: NormalizedPath<'b>,
) -> Vec<(NormalizedPath<'b>, &'b Value)> {
if matches!(self.kind, QuerySegmentKind::Descendant) {
let mut result = self.segment.query_paths(current, root, parent.clone());
result.append(&mut descend_paths(self, current, root, parent));
result
} else {
self.segment.query_paths(current, root, parent)
}
}
}

Expand All @@ -81,6 +87,25 @@ fn descend<'b>(segment: &QuerySegment, current: &'b Value, root: &'b Value) -> V
query
}

fn descend_paths<'b>(
segment: &QuerySegment,
current: &'b Value,
root: &'b Value,
parent: NormalizedPath<'b>,
) -> Vec<(NormalizedPath<'b>, &'b Value)> {
let mut result = Vec::new();
if let Some(list) = current.as_array() {
for (i, v) in list.iter().enumerate() {
result.append(&mut segment.query_paths(v, root, parent.clone_and_push(i)));
}
} else if let Some(obj) = current.as_object() {
for (k, v) in obj {
result.append(&mut segment.query_paths(v, root, parent.clone_and_push(k)));
}
}
result
}

/// Represents the different forms of JSONPath segment
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Segment {
Expand Down Expand Up @@ -187,9 +212,34 @@ impl Queryable for Segment {
fn query_paths<'b>(
&self,
current: &'b Value,
_root: &'b Value,
parent: super::path::NormalizedPath<'b>,
) -> Vec<super::path::NormalizedPath<'b>> {
todo!()
root: &'b Value,
mut parent: NormalizedPath<'b>,
) -> Vec<(NormalizedPath<'b>, &'b Value)> {
let mut result = vec![];
match self {
Segment::LongHand(selectors) => {
for s in selectors {
result.append(&mut s.query_paths(current, root, parent.clone()));
}
}
Segment::DotName(name) => {
if let Some((k, v)) = current.as_object().and_then(|o| o.get_key_value(name)) {
parent.push(k);
result.push((parent, v));
}
}
Segment::Wildcard => {
if let Some(list) = current.as_array() {
for (i, v) in list.iter().enumerate() {
result.push((parent.clone_and_push(i), v));
}
} else if let Some(obj) = current.as_object() {
for (k, v) in obj {
result.push((parent.clone_and_push(k), v));
}
}
}
}
result
}
}
40 changes: 18 additions & 22 deletions serde_json_path_core/src/spec/selector/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use serde_json::{Number, Value};

use crate::spec::{
functions::{FunctionExpr, JsonPathValue, Validated},
path::NormalizedPath,
query::{Query, QueryKind, Queryable},
segment::{QuerySegment, Segment},
};
Expand Down Expand Up @@ -73,10 +74,23 @@ impl Queryable for Filter {
fn query_paths<'b>(
&self,
current: &'b Value,
_root: &'b Value,
parent: crate::spec::path::NormalizedPath<'b>,
) -> Vec<crate::spec::path::NormalizedPath<'b>> {
todo!()
root: &'b Value,
parent: NormalizedPath<'b>,
) -> Vec<(NormalizedPath<'b>, &'b Value)> {
if let Some(list) = current.as_array() {
list.iter()
.enumerate()
.filter(|(_, v)| self.0.test_filter(v, root))
.map(|(i, v)| (parent.clone_and_push(i), v))
.collect()
} else if let Some(obj) = current.as_object() {
obj.iter()
.filter(|(_, v)| self.0.test_filter(v, root))
.map(|(k, v)| (parent.clone_and_push(k), v))
.collect()
} else {
vec![]
}
}
}

Expand Down Expand Up @@ -567,24 +581,6 @@ 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) {
Some(v) => vec![v],
None => vec![],
}
}

fn query_paths<'b>(
&self,
current: &'b Value,
_root: &'b Value,
parent: crate::spec::path::NormalizedPath<'b>,
) -> Vec<crate::spec::path::NormalizedPath<'b>> {
todo!()
}
}

impl std::fmt::Display for SingularQuery {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.kind {
Expand Down
11 changes: 6 additions & 5 deletions serde_json_path_core/src/spec/selector/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,22 @@ impl Queryable for Index {
current: &'b Value,
_root: &'b Value,
mut parent: NormalizedPath<'b>,
) -> Vec<NormalizedPath<'b>> {
if let Some(index) = current.as_array().and_then(|list| {
) -> Vec<(NormalizedPath<'b>, &'b Value)> {
if let Some((index, value)) = current.as_array().and_then(|list| {
if self.0 < 0 {
self.0
.checked_abs()
.and_then(|i| usize::try_from(i).ok())
.and_then(|i| list.len().checked_sub(i))
.and_then(|i| list.get(i).map(|v| (i, v)))
} else {
usize::try_from(self.0)
.ok()
.and_then(|i| (list.len() >= i).then_some(i))
.and_then(|i| list.get(i).map(|v| (i, v)))
}
}) {
parent.push(index.into());
vec![parent]
parent.push(index);
vec![(parent, value)]
} else {
vec![]
}
Expand Down
12 changes: 6 additions & 6 deletions serde_json_path_core/src/spec/selector/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,25 +79,25 @@ impl Queryable for Selector {
current: &'b Value,
root: &'b Value,
parent: NormalizedPath<'b>,
) -> Vec<NormalizedPath<'b>> {
) -> Vec<(NormalizedPath<'b>, &'b Value)> {
match self {
Selector::Name(name) => name.query_paths(current, root, parent),
Selector::Wildcard => {
if let Some(list) = current.as_array() {
list.iter()
.enumerate()
.map(|(i, _)| {
.map(|(i, v)| {
let mut new_path = parent.clone();
new_path.push(PathElement::from(i));
new_path
(new_path, v)
})
.collect()
} else if let Some(obj) = current.as_object() {
obj.keys()
.map(|k| {
obj.iter()
.map(|(k, v)| {
let mut new_path = parent.clone();
new_path.push(PathElement::from(k));
new_path
(new_path, v)
})
.collect()
} else {
Expand Down
8 changes: 4 additions & 4 deletions serde_json_path_core/src/spec/selector/name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ impl Queryable for Name {
current: &'b Value,
_root: &'b Value,
mut parent: NormalizedPath<'b>,
) -> Vec<NormalizedPath<'b>> {
if let Some((s, _)) = current.as_object().and_then(|o| o.get_key_value(&self.0)) {
parent.push(s.into());
vec![parent]
) -> Vec<(NormalizedPath<'b>, &'b Value)> {
if let Some((s, v)) = current.as_object().and_then(|o| o.get_key_value(&self.0)) {
parent.push(s);
vec![(parent, v)]
} else {
vec![]
}
Expand Down
Loading

0 comments on commit cca17b4

Please sign in to comment.