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}