serde_json_path_core/
path.rs

1//! Types for representing [Normalized Paths][norm-paths] from the JSONPath specification
2//!
3//! [norm-paths]: https://www.rfc-editor.org/rfc/rfc9535.html#name-normalized-paths
4use std::{
5    cmp::Ordering,
6    fmt::Display,
7    slice::{Iter, SliceIndex},
8};
9
10use serde::Serialize;
11
12// Documented in the serde_json_path crate, for linking purposes
13#[allow(missing_docs)]
14#[derive(Debug, Default, Eq, PartialEq, Clone, PartialOrd)]
15pub struct NormalizedPath<'a>(Vec<PathElement<'a>>);
16
17impl<'a> NormalizedPath<'a> {
18    pub(crate) fn push<T: Into<PathElement<'a>>>(&mut self, elem: T) {
19        self.0.push(elem.into())
20    }
21
22    pub(crate) fn clone_and_push<T: Into<PathElement<'a>>>(&self, elem: T) -> Self {
23        let mut new_path = self.clone();
24        new_path.push(elem.into());
25        new_path
26    }
27
28    /// Get the [`NormalizedPath`] as a [JSON Pointer][json-pointer] string
29    ///
30    /// This can be used with the [`serde_json::Value::pointer`] or
31    /// [`serde_json::Value::pointer_mut`] methods.
32    ///
33    /// # Example
34    /// ```rust
35    /// # use serde_json::json;
36    /// # use serde_json_path::JsonPath;
37    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
38    /// let mut value = json!({"foo": ["bar", "baz"]});
39    /// let path = JsonPath::parse("$.foo[? @ == 'bar']")?;
40    /// let pointer= path
41    ///     .query_located(&value)
42    ///     .exactly_one()?
43    ///     .location()
44    ///     .to_json_pointer();
45    /// *value.pointer_mut(&pointer).unwrap() = "bop".into();
46    /// assert_eq!(value, json!({"foo": ["bop", "baz"]}));
47    /// # Ok(())
48    /// # }
49    /// ```
50    ///
51    /// [json-pointer]: https://datatracker.ietf.org/doc/html/rfc6901
52    pub fn to_json_pointer(&self) -> String {
53        self.0
54            .iter()
55            .map(PathElement::to_json_pointer)
56            .fold(String::from(""), |mut acc, s| {
57                acc.push('/');
58                acc.push_str(&s);
59                acc
60            })
61    }
62
63    /// Check if the [`NormalizedPath`] is empty
64    ///
65    /// An empty normalized path represents the location of the root node of the JSON object,
66    /// i.e., `$`.
67    pub fn is_empty(&self) -> bool {
68        self.0.is_empty()
69    }
70
71    /// Get the length of the [`NormalizedPath`]
72    pub fn len(&self) -> usize {
73        self.0.len()
74    }
75
76    /// Get an iterator over the [`PathElement`]s of the [`NormalizedPath`]
77    ///
78    /// Note that [`NormalizedPath`] also implements [`IntoIterator`]
79    ///
80    /// # Example
81    /// ```rust
82    /// # use serde_json::json;
83    /// # use serde_json_path::JsonPath;
84    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
85    /// let mut value = json!({"foo": {"bar": 1, "baz": 2, "bop": 3}});
86    /// let path = JsonPath::parse("$.foo[? @ == 2]")?;
87    /// let location = path.query_located(&value).exactly_one()?.to_location();
88    /// let elements: Vec<String> = location
89    ///     .iter()
90    ///     .map(|ele| ele.to_string())
91    ///     .collect();
92    /// assert_eq!(elements, ["foo", "baz"]);
93    /// # Ok(())
94    /// # }
95    /// ```
96    pub fn iter(&self) -> Iter<'_, PathElement<'a>> {
97        self.0.iter()
98    }
99
100    /// Get the [`PathElement`] at `index`, or `None` if the index is out of bounds
101    ///
102    /// # Example
103    /// ```rust
104    /// # use serde_json::json;
105    /// # use serde_json_path::JsonPath;
106    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
107    /// let value = json!({"foo": {"bar": {"baz": "bop"}}});
108    /// let path = JsonPath::parse("$..baz")?;
109    /// let location = path.query_located(&value).exactly_one()?.to_location();
110    /// assert_eq!(location.to_string(), "$['foo']['bar']['baz']");
111    /// assert!(location.get(0).is_some_and(|p| p == "foo"));
112    /// assert!(location.get(1..).is_some_and(|p| p == ["bar", "baz"]));
113    /// assert!(location.get(3).is_none());
114    /// # Ok(())
115    /// # }
116    /// ```
117    pub fn get<I>(&self, index: I) -> Option<&I::Output>
118    where
119        I: SliceIndex<[PathElement<'a>]>,
120    {
121        self.0.get(index)
122    }
123
124    /// Get the first [`PathElement`], or `None` if the path is empty
125    ///
126    /// # Example
127    /// ```rust
128    /// # use serde_json::json;
129    /// # use serde_json_path::JsonPath;
130    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
131    /// let value = json!(["foo", true, {"bar": false}, {"bar": true}]);
132    /// let path = JsonPath::parse("$..[? @ == false]")?;
133    /// let location = path.query_located(&value).exactly_one()?.to_location();
134    /// assert_eq!(location.to_string(), "$[2]['bar']");
135    /// assert!(location.first().is_some_and(|p| *p == 2));
136    /// # Ok(())
137    /// # }
138    /// ```
139    pub fn first(&self) -> Option<&PathElement<'a>> {
140        self.0.first()
141    }
142
143    /// Get the last [`PathElement`], or `None` if the path is empty
144    ///
145    /// # Example
146    /// ```rust
147    /// # use serde_json::json;
148    /// # use serde_json_path::JsonPath;
149    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
150    /// let value = json!({"foo": {"bar": [1, 2, 3]}});
151    /// let path = JsonPath::parse("$..[? @ == 2]")?;
152    /// let location = path.query_located(&value).exactly_one()?.to_location();
153    /// assert_eq!(location.to_string(), "$['foo']['bar'][1]");
154    /// assert!(location.last().is_some_and(|p| *p == 1));
155    /// # Ok(())
156    /// # }
157    /// ```
158    pub fn last(&self) -> Option<&PathElement<'a>> {
159        self.0.last()
160    }
161}
162
163impl<'a> IntoIterator for NormalizedPath<'a> {
164    type Item = PathElement<'a>;
165
166    type IntoIter = std::vec::IntoIter<Self::Item>;
167
168    fn into_iter(self) -> Self::IntoIter {
169        self.0.into_iter()
170    }
171}
172
173impl Display for NormalizedPath<'_> {
174    /// Format the [`NormalizedPath`] as a JSONPath string using the canonical bracket notation
175    /// as per the [JSONPath Specification][norm-paths]
176    ///
177    /// # Example
178    /// ```rust
179    /// # use serde_json::json;
180    /// # use serde_json_path::JsonPath;
181    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
182    /// let value = json!({"foo": ["bar", "baz"]});
183    /// let path = JsonPath::parse("$.foo[0]")?;
184    /// let location = path.query_located(&value).exactly_one()?.to_location();
185    /// assert_eq!(location.to_string(), "$['foo'][0]");
186    /// # Ok(())
187    /// # }
188    /// ```
189    ///
190    /// [norm-paths]: https://www.rfc-editor.org/rfc/rfc9535.html#name-normalized-paths
191    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192        write!(f, "$")?;
193        for elem in &self.0 {
194            match elem {
195                PathElement::Name(name) => write!(f, "['{name}']")?,
196                PathElement::Index(index) => write!(f, "[{index}]")?,
197            }
198        }
199        Ok(())
200    }
201}
202
203impl Serialize for NormalizedPath<'_> {
204    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
205    where
206        S: serde::Serializer,
207    {
208        serializer.serialize_str(self.to_string().as_str())
209    }
210}
211
212/// An element within a [`NormalizedPath`]
213#[derive(Debug, Eq, PartialEq, Clone)]
214pub enum PathElement<'a> {
215    /// A key within a JSON object
216    Name(&'a str),
217    /// An index of a JSON Array
218    Index(usize),
219}
220
221impl PathElement<'_> {
222    fn to_json_pointer(&self) -> String {
223        match self {
224            PathElement::Name(s) => s.replace('~', "~0").replace('/', "~1"),
225            PathElement::Index(i) => i.to_string(),
226        }
227    }
228
229    /// Get the underlying name if the [`PathElement`] is `Name`, or `None` otherwise
230    pub fn as_name(&self) -> Option<&str> {
231        match self {
232            PathElement::Name(n) => Some(n),
233            PathElement::Index(_) => None,
234        }
235    }
236
237    /// Get the underlying index if the [`PathElement`] is `Index`, or `None` otherwise
238    pub fn as_index(&self) -> Option<usize> {
239        match self {
240            PathElement::Name(_) => None,
241            PathElement::Index(i) => Some(*i),
242        }
243    }
244
245    /// Test if the [`PathElement`] is `Name`
246    pub fn is_name(&self) -> bool {
247        self.as_name().is_some()
248    }
249
250    /// Test if the [`PathElement`] is `Index`
251    pub fn is_index(&self) -> bool {
252        self.as_index().is_some()
253    }
254}
255
256impl PartialOrd for PathElement<'_> {
257    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
258        match (self, other) {
259            (PathElement::Name(a), PathElement::Name(b)) => a.partial_cmp(b),
260            (PathElement::Index(a), PathElement::Index(b)) => a.partial_cmp(b),
261            _ => None,
262        }
263    }
264}
265
266impl PartialEq<str> for PathElement<'_> {
267    fn eq(&self, other: &str) -> bool {
268        match self {
269            PathElement::Name(s) => s.eq(&other),
270            PathElement::Index(_) => false,
271        }
272    }
273}
274
275impl PartialEq<&str> for PathElement<'_> {
276    fn eq(&self, other: &&str) -> bool {
277        match self {
278            PathElement::Name(s) => s.eq(other),
279            PathElement::Index(_) => false,
280        }
281    }
282}
283
284impl PartialEq<usize> for PathElement<'_> {
285    fn eq(&self, other: &usize) -> bool {
286        match self {
287            PathElement::Name(_) => false,
288            PathElement::Index(i) => i.eq(other),
289        }
290    }
291}
292
293impl Display for PathElement<'_> {
294    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
295        match self {
296            PathElement::Name(n) => {
297                // https://www.rfc-editor.org/rfc/rfc9535#section-2.7
298                for c in n.chars() {
299                    match c {
300                        '\u{0008}' => write!(f, r#"\b"#)?, // b BS backspace
301                        '\u{000C}' => write!(f, r#"\f"#)?, // f FF form feed
302                        '\u{000A}' => write!(f, r#"\n"#)?, // n LF line feed
303                        '\u{000D}' => write!(f, r#"\r"#)?, // r CR carriage return
304                        '\u{0009}' => write!(f, r#"\t"#)?, // t HT horizontal tab
305                        '\u{0027}' => write!(f, r#"\'"#)?, // ' apostrophe
306                        '\u{005C}' => write!(f, r#"\"#)?,  // \ backslash (reverse solidus)
307                        ('\x00'..='\x07') | '\x0b' | '\x0e' | '\x0f' => {
308                            // "00"-"07", "0b", "0e"-"0f"
309                            write!(f, "\\u000{:x}", c as i32)?
310                        }
311                        _ => write!(f, "{c}")?,
312                    }
313                }
314                Ok(())
315            }
316            PathElement::Index(i) => write!(f, "{i}"),
317        }
318    }
319}
320
321impl<'a> From<&'a String> for PathElement<'a> {
322    fn from(s: &'a String) -> Self {
323        Self::Name(s.as_str())
324    }
325}
326
327impl From<usize> for PathElement<'_> {
328    fn from(index: usize) -> Self {
329        Self::Index(index)
330    }
331}
332
333impl Serialize for PathElement<'_> {
334    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
335    where
336        S: serde::Serializer,
337    {
338        match self {
339            PathElement::Name(s) => serializer.serialize_str(s),
340            PathElement::Index(i) => serializer.serialize_u64(*i as u64),
341        }
342    }
343}
344
345#[cfg(test)]
346mod tests {
347    use super::{NormalizedPath, PathElement};
348
349    #[test]
350    fn normalized_path_to_json_pointer() {
351        let np = NormalizedPath(vec![
352            PathElement::Name("foo"),
353            PathElement::Index(42),
354            PathElement::Name("bar"),
355        ]);
356        assert_eq!(np.to_json_pointer(), "/foo/42/bar");
357    }
358
359    #[test]
360    fn normalized_path_to_json_pointer_with_escapes() {
361        let np = NormalizedPath(vec![
362            PathElement::Name("foo~bar"),
363            PathElement::Index(42),
364            PathElement::Name("baz/bop"),
365        ]);
366        assert_eq!(np.to_json_pointer(), "/foo~0bar/42/baz~1bop");
367    }
368
369    #[test]
370    fn normalized_element_fmt() {
371        for (name, elem, exp) in [
372            ("simple name", PathElement::Name("foo"), "foo"),
373            ("index", PathElement::Index(1), "1"),
374            ("escape_apostrophes", PathElement::Name("'hi'"), r#"\'hi\'"#),
375            (
376                "escapes",
377                PathElement::Name(r#"'\b\f\n\r\t\\'"#),
378                r#"\'\b\f\n\r\t\\\'"#,
379            ),
380            (
381                "escape_vertical_unicode",
382                PathElement::Name("\u{000B}"),
383                r#"\u000b"#,
384            ),
385            (
386                "escape_unicode_null",
387                PathElement::Name("\u{0000}"),
388                r#"\u0000"#,
389            ),
390            (
391                "escape_unicode_runes",
392                PathElement::Name(
393                    "\u{0001}\u{0002}\u{0003}\u{0004}\u{0005}\u{0006}\u{0007}\u{000e}\u{000F}",
394                ),
395                r#"\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u000e\u000f"#,
396            ),
397        ] {
398            assert_eq!(exp, elem.to_string(), "{name}");
399        }
400    }
401}