jsonp/lib.rs
1//! # jsonp
2//!
3//! Fast, zero copy Json pointers.
4//!
5//! This library leverages [serde](https://serde.rs/) and [serde_json](https://docs.serde.rs/serde_json/index.html)
6//! to provide fast, easy to use, on demand deserialization of Json.
7//!
8//! Ever wanted to retrieve some deeply nested data without all the hassle of defining the required
9//! Rust structures, or allocating multiple times into a `serde_json::Value`? No problem:
10//!
11//! ```json
12//! {
13//! "some": {
14//! "deeply": [
15//! {
16//! "nested": {
17//! "truth": "the cake is a lie"
18//! }
19//! }
20//! ]
21//! }
22//! }
23//! ```
24//!
25//! ```
26//! # use jsonp::Pointer;
27//! # type Result = std::result::Result<(), Box<dyn std::error::Error>>;
28//! # const NESTED: &str = r#"{"some": {"deeply": [{"nested": {"truth": "the cake is a lie"}}]}}"#;
29//! fn deeply_nested(json: &str) -> Result {
30//! let p = Pointer::default();
31//!
32//! let truth: &str = p.dotted(json, ".some.deeply.0.nested.truth")?;
33//!
34//! assert_eq!(truth, "the cake is a lie");
35//!
36//! Ok(())
37//! }
38//! # deeply_nested(NESTED).unwrap();
39//! ```
40//! Leveraging serde's [zero copy](https://serde.rs/lifetimes.html#understanding-deserializer-lifetimes) deserialization
41//! we _borrow_ the deeply nested `truth` right out of the backing Json data.
42//!
43//! ## Pointer
44//!
45//! The core structure of this library is the [`jsonp::Pointer`][pointer]. It provides several
46//! methods of dereferencing into Json:
47//!
48//! - [`Pointer::with_segments`][with_segments]
49//! - [`Pointer::with_pattern`][with_pattern]
50//! - [`Pointer::dotted`][dotted]
51//!
52//! While `Pointer::with_segments` provides the most control over exactly how each
53//! [`Segment`][segment] is generated, the other two -- `Pointer::with_pattern` and
54//! `Pointer::dotted` -- make some assumptions about how the pointer string is handled. These are:
55//!
56//! 1. Passing in an empty pointer (or pattern) string will default to deserializing the entire
57//! backing Json.
58//! 2. If the pointer and pattern are equal, same as above.
59//! 3. A pointer starting with a pattern is equivalent to one that doesn't. For example,
60//! `dotted(".foo")` and `dotted("foo")` both result in `foo` being dereferenced.
61//!
62//! ## Mode
63//!
64//! [`jsonp::Mode`][mode] controls how `jsonp` interprets pointer segments. It has two settings,
65//! Late -- the default -- and Early.
66//!
67//! ### Late
68//!
69//! Lazily attempts to coerce pointer segments to map keys or array indexes during deserialization.
70//! This provides maximum flexibility and allows one to deserialize _numeric_ map keys, i.e "42":
71//! "...", but also has the potential to improperly deserialize an array where a map was expected,
72//! or vice versa.
73//!
74//! ### Early
75//!
76//! Decides on initialization whether a given pointer segment is a numeric index or string key.
77//! Guarantees that backing Json object agrees with its expected layout, erroring out otherwise.
78//!
79//! ## Helpers
80//!
81//! This library also provides a few convenience wrapper structs around
82//! [`jsonp::Pointer`][pointer]. These provide pleasant interfaces if you're planning on using a
83//! `Pointer` to repeatedly dereference from a single backing Json structure, and reduce some of
84//! the generics and lifetime noise in function signatures.
85//!
86//! - [`jsonp::BackingStr`][b_str]
87//! - [`jsonp::BackingJson`][b_json]
88//! - [`jsonp::BackingBytes`][b_bytes]
89//!
90//! [pointer]: Pointer
91//! [mode]: Mode
92//! [segment]: Segment
93//! [b_str]: BackingStr
94//! [b_json]: BackingJson
95//! [b_bytes]: BackingBytes
96//! [with_segments]: Pointer::with_segments
97//! [with_pattern]: Pointer::with_pattern
98//! [dotted]: Pointer::dotted
99
100use {
101 json::value::RawValue as RawJson,
102 serde::{de::Deserializer as _, Deserialize},
103 serde_json as json,
104 std::iter::IntoIterator,
105 visitor::{ArrayVisitor, LazyVisitor, MapVisitor},
106};
107
108mod to_raw;
109mod visitor;
110
111pub use to_raw::ToRaw;
112
113/// The heart of this library, this structure contains all of the base
114/// functionality to dereference into borrowed Json structures.
115///
116/// While this struct does not implement [`Copy`][std::marker::Copy], it is extremely cheap to
117/// clone and can be done liberally.
118#[derive(Debug, Default, Clone)]
119pub struct Pointer {
120 mode: Mode,
121}
122
123impl Pointer {
124 /// Instantiate a new pointer with the given mode
125 pub fn new(mode: Mode) -> Self {
126 Self { mode }
127 }
128
129 /// Convenience function for using the common dot (`.`) delimited format for dereferencing
130 /// nested Json structures.
131 ///
132 /// # Example
133 ///
134 /// ```
135 /// use jsonp::Pointer;
136 ///
137 /// let json = r#"{"outer": {"array": [0, "one", true]}}"#;
138 /// let one: &str = Pointer::default().dotted(json, "outer.array.1").unwrap();
139 ///
140 /// assert!(one == "one");
141 /// ```
142 pub fn dotted<'de, 'j: 'de, J, T>(&self, backing: &'j J, pointer: &str) -> Result<T, J::Error>
143 where
144 J: ToRaw<'j> + ?Sized,
145 T: Deserialize<'de>,
146 {
147 self.with_pattern(backing, pointer, ".")
148 }
149
150 /// Dereference using the given pointer and pattern. The pointer is split into
151 /// segments using the pattern. Starting the pointer with or without the pattern
152 /// is equivalent, i.e: `with_pattern(..., ".foo", ".")` is equal to
153 /// `with_pattern(..., "foo", ".")`.
154 ///
155 /// Attempting to pass in either an empty pointer or pattern will cause this function
156 /// to short circuit any dereferencing and attempt deserialization from `backing`
157 /// directly.
158 ///
159 /// # Example
160 ///
161 /// ```
162 /// use jsonp::Pointer;
163 ///
164 /// let json = r#"{"outer": {"array": [0, "one", true]}}"#;
165 /// let is_true: bool = Pointer::default().with_pattern(json, "outer array 2", " ").unwrap();
166 ///
167 /// assert!(is_true);
168 /// ```
169 pub fn with_pattern<'de, 'j: 'de, J, T>(
170 &self,
171 backing: &'j J,
172 pointer: &str,
173 pattern: &str,
174 ) -> Result<T, J::Error>
175 where
176 J: ToRaw<'j> + ?Sized,
177 T: Deserialize<'de>,
178 {
179 let json = backing.try_into_raw()?;
180
181 // If the user attempts to pass any of the annoying edge cases around
182 // pattern splitting into us, we'll simply short circuit any dereferencing
183 // and attempt deserialization of the the entire backing object
184 if pointer.is_empty() || pattern.is_empty() || pointer == pattern {
185 return self.with_segments(json, None).map_err(Into::into);
186 }
187
188 // Allow users to not start a pointer with the given pattern
189 // if they choose. This is special cased to allow for situations
190 // where it would be annoying to require starting the pointer with a
191 // pattern instance, e.g: pat = ", " ptr = "foo, bar, baz".
192 if pointer.starts_with(pattern) {
193 let pointers = pointer.split(pattern).skip(1).map(|s| self.segment(s));
194
195 self.with_segments(json, pointers).map_err(Into::into)
196 } else {
197 let pointers = pointer.split(pattern).map(|s| self.segment(s));
198
199 self.with_segments(json, pointers).map_err(Into::into)
200 }
201 }
202
203 /// Dereference using the given iterable set of segments.
204 ///
205 /// # Example
206 ///
207 /// ```
208 /// use jsonp::{Pointer, Segment};
209 ///
210 /// let json = r#"{"outer": {"array": [0, 1, 2, 3]}}"#;
211 /// let segments = &["outer", "array"];
212 /// let array: Vec<i8> = Pointer::default()
213 /// .with_segments(json, segments.into_iter().copied().map(Segment::lazy))
214 /// .unwrap();
215 ///
216 /// assert_eq!(&array, &[0, 1, 2, 3]);
217 /// ```
218 pub fn with_segments<'de, 'j: 'de, 'p, J, I, T>(
219 &self,
220 backing: &'j J,
221 segments: I,
222 ) -> Result<T, J::Error>
223 where
224 J: ToRaw<'j> + ?Sized,
225 I: IntoIterator<Item = Segment<'p>>,
226 T: Deserialize<'de>,
227 {
228 let json = backing.try_into_raw()?;
229
230 inner(json, segments.into_iter()).map_err(Into::into)
231 }
232
233 fn segment<'p>(&self, s: &'p str) -> Segment<'p> {
234 match self.mode {
235 Mode::Late => Segment::lazy(s),
236 Mode::Early => Segment::early(s),
237 }
238 }
239}
240
241/// Convenience wrapper around the library core functions for string slices,
242/// removing some of the generic noise from function signatures.
243#[derive(Debug, Clone)]
244pub struct BackingStr<'a> {
245 p: Pointer,
246 borrow: &'a str,
247}
248
249impl<'a> BackingStr<'a> {
250 /// Instantiate a wrapper around the given string slice.
251 pub fn new(borrow: &'a str) -> Self {
252 Self::with(borrow, Default::default())
253 }
254
255 /// Instantiate a wrapper around the given string slice and pointer.
256 pub fn with(borrow: &'a str, p: Pointer) -> Self {
257 Self { p, borrow }
258 }
259
260 /// See the documentation of [`Pointer::dotted`][Pointer::dotted].
261 ///
262 /// # Example
263 ///
264 /// ```
265 /// use jsonp::BackingStr;
266 ///
267 /// let json = r#"{"outer": {"array": [0, "one", true]}}"#;
268 /// let one: &str = BackingStr::new(json).dotted("outer.array.1").unwrap();
269 ///
270 /// assert!(one == "one");
271 /// ```
272 pub fn dotted<'de, T>(&self, pointer: &str) -> Result<T, json::Error>
273 where
274 T: Deserialize<'de>,
275 'a: 'de,
276 {
277 self.p.dotted(self.borrow, pointer)
278 }
279
280 /// See the documentation of [`Pointer::with_pattern`][Pointer::with_pattern].
281 ///
282 /// # Example
283 ///
284 /// ```
285 /// use jsonp::BackingStr;
286 ///
287 /// let json = r#"{"outer": {"array": [0, "one", true]}}"#;
288 /// let is_true: bool = BackingStr::new(json).pattern("outer array 2", " ").unwrap();
289 ///
290 /// assert!(is_true);
291 /// ```
292 pub fn pattern<'de, T>(&self, pointer: &str, pattern: &str) -> Result<T, json::Error>
293 where
294 T: Deserialize<'de>,
295 'a: 'de,
296 {
297 self.p.with_pattern(self.borrow, pointer, pattern)
298 }
299
300 /// See the documentation for [`Pointer::with_segments`][Pointer::with_segments].
301 ///
302 /// # Example
303 ///
304 /// ```
305 /// use jsonp::{BackingStr, Segment};
306 ///
307 /// let json = r#"{"outer": {"array": [0, 1, 2, 3]}}"#;
308 /// let segments = &["outer", "array"];
309 /// let array: Vec<i8> = BackingStr::new(json)
310 /// .pointer(segments.into_iter().copied().map(Segment::lazy))
311 /// .unwrap();
312 ///
313 /// assert_eq!(&array, &[0, 1, 2, 3]);
314 /// ```
315 pub fn pointer<'de, 'p, I, T>(&self, pointers: I) -> Result<T, json::Error>
316 where
317 I: IntoIterator<Item = Segment<'p>>,
318 T: Deserialize<'de>,
319 'a: 'de,
320 {
321 self.p.with_segments(self.borrow, pointers)
322 }
323}
324
325impl<'a> From<&'a str> for BackingStr<'a> {
326 fn from(backing: &'a str) -> Self {
327 Self::new(backing)
328 }
329}
330
331/// Convenience wrapper around the library core functions for raw Json,
332/// removing some of the generic noise from function signatures.
333#[derive(Debug, Clone)]
334pub struct BackingJson<'a> {
335 p: Pointer,
336 borrow: &'a RawJson,
337}
338
339impl<'a> BackingJson<'a> {
340 /// Instantiate a wrapper around the given borrowed raw Json.
341 pub fn new(borrow: &'a RawJson) -> Self {
342 Self::with(borrow, Default::default())
343 }
344
345 /// Instantiate a wrapper around the given borrowed raw Json and pointer.
346 pub fn with(borrow: &'a RawJson, p: Pointer) -> Self {
347 Self { p, borrow }
348 }
349
350 /// See the documentation of [`Pointer::dotted`][Pointer::dotted].
351 ///
352 /// # Example
353 ///
354 /// ```
355 /// use {jsonp::{BackingJson, Segment}, serde_json::from_str};
356 ///
357 /// let json = from_str(r#"{"outer": {"array": [0, "one", true]}}"#).unwrap();
358 /// let one: &str = BackingJson::new(json).dotted("outer.array.1").unwrap();
359 ///
360 /// assert!(one == "one");
361 /// ```
362 pub fn dotted<'de, T>(&self, pointer: &str) -> Result<T, json::Error>
363 where
364 T: Deserialize<'de>,
365 'a: 'de,
366 {
367 self.p.dotted(self.borrow, pointer)
368 }
369
370 /// See the documentation of [`Pointer::with_pattern`][Pointer::with_pattern].
371 ///
372 /// # Example
373 ///
374 /// ```
375 /// use {jsonp::{BackingJson, Segment}, serde_json::from_str};
376 ///
377 /// let json = from_str(r#"{"outer": {"array": [0, "one", true]}}"#).unwrap();
378 /// let is_true: bool = BackingJson::new(json).pattern("outer array 2", " ").unwrap();
379 ///
380 /// assert!(is_true);
381 /// ```
382 pub fn pattern<'de, T>(&self, pointer: &str, pattern: &str) -> Result<T, json::Error>
383 where
384 T: Deserialize<'de>,
385 'a: 'de,
386 {
387 self.p.with_pattern(self.borrow, pointer, pattern)
388 }
389
390 /// See the documentation for [`Pointer::with_segments`][Pointer::with_segments].
391 ///
392 /// # Example
393 ///
394 /// ```
395 /// use {jsonp::{BackingJson, Segment}, serde_json::from_str};
396 ///
397 /// let json = from_str(r#"{"outer": {"array": [0, 1, 2, 3]}}"#).unwrap();
398 /// let segments = &["outer", "array"];
399 /// let array: Vec<i8> = BackingJson::new(json)
400 /// .pointer(segments.into_iter().copied().map(Segment::lazy))
401 /// .unwrap();
402 ///
403 /// assert_eq!(&array, &[0, 1, 2, 3]);
404 /// ```
405 pub fn pointer<'de, 'p, I, T>(&self, pointers: I) -> Result<T, json::Error>
406 where
407 I: IntoIterator<Item = Segment<'p>>,
408 T: Deserialize<'de>,
409 'a: 'de,
410 {
411 self.p.with_segments(self.borrow, pointers)
412 }
413}
414
415impl<'a> From<&'a RawJson> for BackingJson<'a> {
416 fn from(backing: &'a RawJson) -> Self {
417 Self::new(backing)
418 }
419}
420
421/// Convenience wrapper around the library core functions for byte slices,
422/// removing some of the generic noise from function signatures.
423#[derive(Debug, Clone)]
424pub struct BackingBytes<'a> {
425 p: Pointer,
426 borrow: &'a [u8],
427}
428
429impl<'a> BackingBytes<'a> {
430 /// Instantiate a wrapper around the given byte slice.
431 pub fn new(borrow: &'a [u8]) -> Self {
432 Self::with(borrow, Default::default())
433 }
434
435 /// Instantiate a wrapper around the given byte slice and pointer.
436 pub fn with(borrow: &'a [u8], pointer: Pointer) -> Self {
437 Self { p: pointer, borrow }
438 }
439
440 /// See the documentation of [`Pointer::dotted`][Pointer::dotted].
441 ///
442 /// # Example
443 ///
444 /// ```
445 /// use jsonp::BackingBytes;
446 ///
447 /// let json = r#"{"outer": {"array": [0, "one", true]}}"#.as_bytes();
448 /// let one: &str = BackingBytes::new(json).dotted("outer.array.1").unwrap();
449 ///
450 /// assert!(one == "one");
451 /// ```
452 pub fn dotted<'de, T>(&self, pointer: &str) -> Result<T, json::Error>
453 where
454 T: Deserialize<'de>,
455 'a: 'de,
456 {
457 self.p.dotted(self.borrow, pointer)
458 }
459
460 /// See the documentation of [`Pointer::with_pattern`][Pointer::with_pattern].
461 ///
462 /// # Example
463 ///
464 /// ```
465 /// use jsonp::BackingBytes;
466 ///
467 /// let json = r#"{"outer": {"array": [0, "one", true]}}"#.as_bytes();
468 /// let is_true: bool = BackingBytes::new(json).pattern("outer array 2", " ").unwrap();
469 ///
470 /// assert!(is_true);
471 /// ```
472 pub fn pattern<'de, T>(&self, pointer: &str, pattern: &str) -> Result<T, json::Error>
473 where
474 T: Deserialize<'de>,
475 'a: 'de,
476 {
477 self.p.with_pattern(self.borrow, pointer, pattern)
478 }
479
480 /// See the documentation for [`Pointer::with_segments`][Pointer::with_segments].
481 ///
482 /// # Example
483 ///
484 /// ```
485 /// use jsonp::{BackingBytes, Segment};
486 ///
487 /// let json = r#"{"outer": {"array": [0, 1, 2, 3]}}"#.as_bytes();
488 /// let segments = &["outer", "array"];
489 /// let array: Vec<i8> = BackingBytes::new(json)
490 /// .pointer(segments.into_iter().copied().map(Segment::lazy))
491 /// .unwrap();
492 ///
493 /// assert_eq!(&array, &[0, 1, 2, 3]);
494 /// ```
495 pub fn pointer<'de, 'p, I, T>(&self, pointers: I) -> Result<T, json::Error>
496 where
497 I: IntoIterator<Item = Segment<'p>>,
498 T: Deserialize<'de>,
499 'a: 'de,
500 {
501 self.p.with_segments(self.borrow, pointers)
502 }
503}
504
505impl<'a> From<&'a [u8]> for BackingBytes<'a> {
506 fn from(backing: &'a [u8]) -> Self {
507 Self::new(backing)
508 }
509}
510
511/// Set the mode for interpreting pointer segments.
512///
513/// The default, Late lazily types each segment waiting until
514/// deserialization to determine if the segment is a map key or
515/// array index. Early parses each segment as soon as it's handled.
516/// For more information, see `Segment`.
517#[derive(Debug, Clone, Copy, PartialEq, Eq)]
518pub enum Mode {
519 Late,
520 Early,
521}
522
523impl Default for Mode {
524 fn default() -> Self {
525 Self::Late
526 }
527}
528
529/// Represents a segment of a pointer
530#[derive(Debug, Clone, Copy)]
531pub struct Segment<'p> {
532 inner: PKind<'p>,
533}
534
535/// Typed representation of pointer segments
536#[derive(Debug, Clone, Copy)]
537enum PKind<'p> {
538 /// Delayed segment typing
539 Lazy(&'p str),
540 /// A map's key
541 Key(&'p str),
542 /// An index into an array
543 Index(u64),
544}
545
546impl<'p> Segment<'p> {
547 /// Lazily type the pointer, delaying the declaration
548 /// until called by the Json deserializer.
549 ///
550 /// Note this allows for processing of all valid Json
551 /// map keys; however, using this type _can_ also deserialize
552 /// Json arrays if the key is is parsable as a number.
553 ///
554 /// If you need strongly typed pointers see the `key` and
555 /// `index` methods.
556 pub fn lazy(s: &'p str) -> Self {
557 Self {
558 inner: PKind::Lazy(s),
559 }
560 }
561
562 /// Parse a pointer from a string slice, by attempting to convert it
563 /// to a number, and if successful setting it as an array index, otherwise
564 /// using it as a map key.
565 pub fn early(s: &'p str) -> Self {
566 use std::str::FromStr;
567
568 if s.is_empty() {
569 return Self::key("");
570 }
571
572 let inner = match u64::from_str(s).ok() {
573 Some(n) => PKind::Index(n),
574 None => PKind::Key(s),
575 };
576
577 Self { inner }
578 }
579
580 /// Generate a new map key segment
581 pub fn key(s: &'p str) -> Self {
582 Self {
583 inner: PKind::Key(s),
584 }
585 }
586
587 /// Generate a new array index segment
588 pub fn index(idx: u64) -> Self {
589 Self {
590 inner: PKind::Index(idx),
591 }
592 }
593}
594
595// Crate workhorse, this function abuses Rust's reference guarantees
596// to iteratively drill down a nested Json structure
597fn inner<'de, 'a: 'de, 'p, I, T>(j: &'a RawJson, p: I) -> Result<T, json::Error>
598where
599 I: Iterator<Item = Segment<'p>>,
600 T: Deserialize<'de>,
601{
602 use json::Deserializer;
603 let mut target = j;
604
605 for ptr in p {
606 let mut de = Deserializer::from_str(target.get());
607
608 match ptr.inner {
609 PKind::Lazy(l) => {
610 let value = de.deserialize_any(LazyVisitor::new(l))?;
611
612 target = value;
613 }
614 PKind::Key(k) => {
615 let value = de.deserialize_map(MapVisitor::new(k))?;
616
617 target = value;
618 }
619 PKind::Index(i) => {
620 let value = de.deserialize_seq(ArrayVisitor::new(i))?;
621
622 target = value;
623 }
624 }
625 }
626
627 serde_json::from_str(target.get()).map_err(Into::into)
628}
629
630#[cfg(test)]
631#[allow(unused_imports)]
632mod tests {
633 use super::*;
634 use pretty_assertions::{assert_eq, assert_ne};
635
636 type Result = std::result::Result<(), Box<dyn std::error::Error>>;
637
638 const NESTED: &str =
639 r#"{"foo": {"bar": [0, "1", {"baz": "hello world!" }, "blitz", "fooey" ] } }"#;
640
641 #[test]
642 fn with_segments_str() -> Result {
643 let pointer = &[
644 Segment::key("foo"),
645 Segment::key("bar"),
646 Segment::index(2),
647 Segment::key("baz"),
648 ];
649
650 let output: &str =
651 Pointer::default().with_segments(NESTED, pointer.into_iter().copied())?;
652
653 assert_eq!(output, "hello world!");
654
655 Ok(())
656 }
657
658 #[test]
659 fn with_segments_json() -> Result {
660 let pointer = &[
661 Segment::key("foo"),
662 Segment::key("bar"),
663 Segment::index(2),
664 Segment::key("baz"),
665 ];
666
667 let json: &RawJson = serde_json::from_str(NESTED)?;
668
669 let output: &str = Pointer::default().with_segments(json, pointer.into_iter().copied())?;
670
671 assert_eq!(output, "hello world!");
672
673 Ok(())
674 }
675
676 #[test]
677 fn with_pattern_str() -> Result {
678 let output: &str = Pointer::default().with_pattern(NESTED, "foo, bar, 2, baz", ", ")?;
679
680 assert_eq!(output, "hello world!");
681
682 Ok(())
683 }
684
685 #[test]
686 fn with_pattern_json() -> Result {
687 let json: &RawJson = serde_json::from_str(NESTED)?;
688
689 let output: &str = Pointer::default().with_pattern(json, "foo, bar, 2, baz", ", ")?;
690
691 assert_eq!(output, "hello world!");
692
693 Ok(())
694 }
695
696 #[test]
697 fn with_pattern_empty() -> Result {
698 let object: &RawJson = json::from_str(NESTED)?;
699 let output: &RawJson = Pointer::default().with_pattern(NESTED, "", ", ")?;
700
701 assert_eq!(output.to_string(), object.to_string());
702
703 Ok(())
704 }
705
706 #[test]
707 fn dotted_str() -> Result {
708 let output: &str = Pointer::default().dotted(NESTED, "foo.bar.2.baz")?;
709
710 assert_eq!(output, "hello world!");
711
712 Ok(())
713 }
714
715 #[test]
716 fn dotted_json() -> Result {
717 let json: &RawJson = serde_json::from_str(NESTED)?;
718
719 let output: &str = Pointer::default().dotted(json, "foo.bar.2.baz")?;
720
721 assert_eq!(output, "hello world!");
722
723 Ok(())
724 }
725
726 #[test]
727 fn dotted_empty() -> Result {
728 let object: &RawJson = json::from_str(NESTED)?;
729 let output: &RawJson = Pointer::default().dotted(NESTED, "")?;
730
731 assert_eq!(output.to_string(), object.to_string());
732
733 Ok(())
734 }
735}