Skip to main content

datavalue_rs/
owned.rs

1//! [`OwnedDataValue`] — heap-owned counterpart to [`DataValue`].
2//!
3//! Use this when a value must outlive its arena: long-lived caches,
4//! function return values across arena boundaries, results stored in
5//! global state, etc. Construction goes through the same fast hand-rolled
6//! parser via a throwaway arena and a deep-clone out of it.
7//!
8//! For hot-path workloads keep using [`DataValue`] — the owned form is
9//! strictly slower (heap allocation per composite node) and exists only
10//! to escape the arena lifetime when needed.
11
12use core::ops::Index;
13
14use bumpalo::Bump;
15
16#[cfg(feature = "datetime")]
17use crate::datetime::{DataDateTime, DataDuration};
18use crate::number::NumberValue;
19use crate::parser::ParseError;
20use crate::value::DataValue;
21
22/// Heap-owned JSON value tree. Variants mirror [`DataValue`] one-for-one;
23/// no lifetime parameter.
24#[derive(Debug, Clone, Default)]
25pub enum OwnedDataValue {
26    #[default]
27    Null,
28    Bool(bool),
29    Number(NumberValue),
30    String(String),
31    Array(Vec<OwnedDataValue>),
32    Object(Vec<(String, OwnedDataValue)>),
33    #[cfg(feature = "datetime")]
34    DateTime(DataDateTime),
35    #[cfg(feature = "datetime")]
36    Duration(DataDuration),
37}
38
39static OWNED_NULL: OwnedDataValue = OwnedDataValue::Null;
40
41impl core::str::FromStr for OwnedDataValue {
42    type Err = ParseError;
43    fn from_str(s: &str) -> Result<Self, Self::Err> {
44        let arena = Bump::new();
45        let v = DataValue::from_str(s, &arena)?;
46        Ok(v.to_owned())
47    }
48}
49
50impl OwnedDataValue {
51    // ---- Construction ----
52
53    /// Parse JSON into an `OwnedDataValue`. Internally parses into a
54    /// throwaway arena (using the fast hand-rolled parser) and deep-clones
55    /// the result out — so JSON parsing speed matches `DataValue::from_str`,
56    /// minus the deep-clone tail.
57    ///
58    /// Also available via the [`std::str::FromStr`] trait.
59    pub fn from_json(input: &str) -> Result<Self, ParseError> {
60        input.parse()
61    }
62
63    // ---- Type predicates ----
64
65    #[inline]
66    pub fn is_null(&self) -> bool {
67        matches!(self, OwnedDataValue::Null)
68    }
69    #[inline]
70    pub fn is_bool(&self) -> bool {
71        matches!(self, OwnedDataValue::Bool(_))
72    }
73    #[inline]
74    pub fn is_number(&self) -> bool {
75        matches!(self, OwnedDataValue::Number(_))
76    }
77    #[inline]
78    pub fn is_i64(&self) -> bool {
79        matches!(self, OwnedDataValue::Number(NumberValue::Integer(_)))
80    }
81    #[inline]
82    pub fn is_f64(&self) -> bool {
83        matches!(self, OwnedDataValue::Number(NumberValue::Float(_)))
84    }
85    #[inline]
86    pub fn is_string(&self) -> bool {
87        matches!(self, OwnedDataValue::String(_))
88    }
89    #[inline]
90    pub fn is_array(&self) -> bool {
91        matches!(self, OwnedDataValue::Array(_))
92    }
93    #[inline]
94    pub fn is_object(&self) -> bool {
95        matches!(self, OwnedDataValue::Object(_))
96    }
97    #[cfg(feature = "datetime")]
98    #[inline]
99    pub fn is_datetime(&self) -> bool {
100        matches!(self, OwnedDataValue::DateTime(_))
101    }
102    #[cfg(feature = "datetime")]
103    #[inline]
104    pub fn is_duration(&self) -> bool {
105        matches!(self, OwnedDataValue::Duration(_))
106    }
107
108    // ---- Accessors ----
109
110    #[inline]
111    pub fn as_bool(&self) -> Option<bool> {
112        match self {
113            OwnedDataValue::Bool(b) => Some(*b),
114            _ => None,
115        }
116    }
117    #[inline]
118    pub fn as_i64(&self) -> Option<i64> {
119        match self {
120            OwnedDataValue::Number(n) => n.as_i64(),
121            _ => None,
122        }
123    }
124    #[inline]
125    pub fn as_f64(&self) -> Option<f64> {
126        match self {
127            OwnedDataValue::Number(n) => Some(n.as_f64()),
128            _ => None,
129        }
130    }
131    #[inline]
132    pub fn as_number(&self) -> Option<&NumberValue> {
133        match self {
134            OwnedDataValue::Number(n) => Some(n),
135            _ => None,
136        }
137    }
138    #[inline]
139    pub fn as_str(&self) -> Option<&str> {
140        match self {
141            OwnedDataValue::String(s) => Some(s.as_str()),
142            _ => None,
143        }
144    }
145    #[inline]
146    pub fn as_array(&self) -> Option<&[OwnedDataValue]> {
147        match self {
148            OwnedDataValue::Array(items) => Some(items.as_slice()),
149            _ => None,
150        }
151    }
152    #[inline]
153    pub fn as_object(&self) -> Option<&[(String, OwnedDataValue)]> {
154        match self {
155            OwnedDataValue::Object(pairs) => Some(pairs.as_slice()),
156            _ => None,
157        }
158    }
159    #[cfg(feature = "datetime")]
160    #[inline]
161    pub fn as_datetime(&self) -> Option<&DataDateTime> {
162        match self {
163            OwnedDataValue::DateTime(d) => Some(d),
164            _ => None,
165        }
166    }
167    #[cfg(feature = "datetime")]
168    #[inline]
169    pub fn as_duration(&self) -> Option<&DataDuration> {
170        match self {
171            OwnedDataValue::Duration(d) => Some(d),
172            _ => None,
173        }
174    }
175
176    /// `serde_json::Value::get`-style lookup.
177    #[inline]
178    pub fn get<I: OwnedValueIndex>(&self, index: I) -> Option<&OwnedDataValue> {
179        I::index_into(&index, self)
180    }
181
182    #[inline]
183    pub fn len(&self) -> Option<usize> {
184        match self {
185            OwnedDataValue::Array(a) => Some(a.len()),
186            OwnedDataValue::Object(o) => Some(o.len()),
187            _ => None,
188        }
189    }
190
191    #[inline]
192    pub fn is_empty(&self) -> Option<bool> {
193        self.len().map(|n| n == 0)
194    }
195
196    /// Iterate array items. Returns an empty iterator if `self` is not an
197    /// array.
198    #[inline]
199    pub fn members(&self) -> core::slice::Iter<'_, OwnedDataValue> {
200        match self {
201            OwnedDataValue::Array(items) => items.iter(),
202            _ => [].iter(),
203        }
204    }
205
206    /// Iterate object entries as `(key, value)` pairs in insertion order.
207    /// Returns an empty iterator if `self` is not an object.
208    #[inline]
209    pub fn entries(&self) -> OwnedEntriesIter<'_> {
210        match self {
211            OwnedDataValue::Object(pairs) => OwnedEntriesIter {
212                inner: pairs.iter(),
213            },
214            _ => OwnedEntriesIter { inner: [].iter() },
215        }
216    }
217
218    /// Serialise to a compact JSON string. Equivalent to `format!("{self}")` /
219    /// `self.to_string()` — provided as the conventional name people reach
220    /// for, and so callers don't have to import `std::fmt::Write`.
221    #[inline]
222    pub fn to_json_string(&self) -> String {
223        self.to_string()
224    }
225
226    /// Borrow this owned tree into the given arena, returning a
227    /// [`DataValue`] view. Strings are arena-allocated copies.
228    ///
229    /// Array/Object use `alloc_slice_fill_with` rather than
230    /// `bumpalo::Vec::with_capacity_in` + push: one pre-sized arena
231    /// allocation and a tight write loop, skipping the Vec wrapper's
232    /// per-push capacity check and the `Layout::array` validation that
233    /// `RawVec::allocate_in` re-runs for each nested allocation.
234    pub fn to_arena<'a>(&self, arena: &'a Bump) -> DataValue<'a> {
235        match self {
236            OwnedDataValue::Null => DataValue::Null,
237            OwnedDataValue::Bool(b) => DataValue::Bool(*b),
238            OwnedDataValue::Number(n) => DataValue::Number(*n),
239            OwnedDataValue::String(s) => DataValue::String(arena.alloc_str(s)),
240            OwnedDataValue::Array(items) => {
241                let slice = arena.alloc_slice_fill_with(items.len(), |i| items[i].to_arena(arena));
242                DataValue::Array(slice)
243            }
244            OwnedDataValue::Object(pairs) => {
245                let slice = arena.alloc_slice_fill_with(pairs.len(), |i| {
246                    let (k, v) = &pairs[i];
247                    (arena.alloc_str(k) as &str, v.to_arena(arena))
248                });
249                DataValue::Object(slice)
250            }
251            #[cfg(feature = "datetime")]
252            OwnedDataValue::DateTime(d) => DataValue::DateTime(*d),
253            #[cfg(feature = "datetime")]
254            OwnedDataValue::Duration(d) => DataValue::Duration(*d),
255        }
256    }
257}
258
259impl<'a> DataValue<'a> {
260    /// Deep-clone this arena-bound tree into an [`OwnedDataValue`] that
261    /// no longer references the arena.
262    pub fn to_owned(&self) -> OwnedDataValue {
263        match *self {
264            DataValue::Null => OwnedDataValue::Null,
265            DataValue::Bool(b) => OwnedDataValue::Bool(b),
266            DataValue::Number(n) => OwnedDataValue::Number(n),
267            DataValue::String(s) => OwnedDataValue::String(s.to_string()),
268            DataValue::Array(items) => {
269                OwnedDataValue::Array(items.iter().map(DataValue::to_owned).collect())
270            }
271            DataValue::Object(pairs) => OwnedDataValue::Object(
272                pairs
273                    .iter()
274                    .map(|(k, v)| ((*k).to_string(), v.to_owned()))
275                    .collect(),
276            ),
277            #[cfg(feature = "datetime")]
278            DataValue::DateTime(d) => OwnedDataValue::DateTime(d),
279            #[cfg(feature = "datetime")]
280            DataValue::Duration(d) => OwnedDataValue::Duration(d),
281        }
282    }
283}
284
285/// Iterator over `(key, value)` pairs in an [`OwnedDataValue::Object`].
286/// Created via [`OwnedDataValue::entries`].
287pub struct OwnedEntriesIter<'v> {
288    inner: core::slice::Iter<'v, (String, OwnedDataValue)>,
289}
290
291impl<'v> Iterator for OwnedEntriesIter<'v> {
292    type Item = (&'v str, &'v OwnedDataValue);
293    #[inline]
294    fn next(&mut self) -> Option<Self::Item> {
295        self.inner.next().map(|(k, v)| (k.as_str(), v))
296    }
297    #[inline]
298    fn size_hint(&self) -> (usize, Option<usize>) {
299        self.inner.size_hint()
300    }
301}
302
303impl ExactSizeIterator for OwnedEntriesIter<'_> {}
304
305impl PartialEq for OwnedDataValue {
306    #[inline]
307    fn eq(&self, other: &Self) -> bool {
308        match (self, other) {
309            (OwnedDataValue::Null, OwnedDataValue::Null) => true,
310            (OwnedDataValue::Bool(a), OwnedDataValue::Bool(b)) => a == b,
311            (OwnedDataValue::Number(a), OwnedDataValue::Number(b)) => a == b,
312            (OwnedDataValue::String(a), OwnedDataValue::String(b)) => a == b,
313            (OwnedDataValue::Array(a), OwnedDataValue::Array(b)) => a == b,
314            (OwnedDataValue::Object(a), OwnedDataValue::Object(b)) => {
315                if a.len() != b.len() {
316                    return false;
317                }
318                a.iter().all(|(k, v)| {
319                    b.iter()
320                        .find(|(bk, _)| bk == k)
321                        .is_some_and(|(_, bv)| v == bv)
322                })
323            }
324            #[cfg(feature = "datetime")]
325            (OwnedDataValue::DateTime(a), OwnedDataValue::DateTime(b)) => a == b,
326            #[cfg(feature = "datetime")]
327            (OwnedDataValue::Duration(a), OwnedDataValue::Duration(b)) => a == b,
328            _ => false,
329        }
330    }
331}
332
333// ---- Index trait dispatch (parallel to ValueIndex for borrowed side) ----
334
335pub trait OwnedValueIndex: private::Sealed {
336    fn index_into<'v>(&self, value: &'v OwnedDataValue) -> Option<&'v OwnedDataValue>;
337    fn index_into_or_null<'v>(&self, value: &'v OwnedDataValue) -> &'v OwnedDataValue;
338}
339
340mod private {
341    pub trait Sealed {}
342    impl Sealed for str {}
343    impl Sealed for String {}
344    impl Sealed for usize {}
345    impl<T: Sealed + ?Sized> Sealed for &T {}
346}
347
348impl OwnedValueIndex for str {
349    #[inline]
350    fn index_into<'v>(&self, value: &'v OwnedDataValue) -> Option<&'v OwnedDataValue> {
351        match value {
352            OwnedDataValue::Object(pairs) => pairs.iter().find(|(k, _)| k == self).map(|(_, v)| v),
353            _ => None,
354        }
355    }
356    #[inline]
357    fn index_into_or_null<'v>(&self, value: &'v OwnedDataValue) -> &'v OwnedDataValue {
358        self.index_into(value).unwrap_or(&OWNED_NULL)
359    }
360}
361
362impl OwnedValueIndex for String {
363    #[inline]
364    fn index_into<'v>(&self, value: &'v OwnedDataValue) -> Option<&'v OwnedDataValue> {
365        self.as_str().index_into(value)
366    }
367    #[inline]
368    fn index_into_or_null<'v>(&self, value: &'v OwnedDataValue) -> &'v OwnedDataValue {
369        self.as_str().index_into_or_null(value)
370    }
371}
372
373impl OwnedValueIndex for usize {
374    #[inline]
375    fn index_into<'v>(&self, value: &'v OwnedDataValue) -> Option<&'v OwnedDataValue> {
376        match value {
377            OwnedDataValue::Array(items) => items.get(*self),
378            _ => None,
379        }
380    }
381    #[inline]
382    fn index_into_or_null<'v>(&self, value: &'v OwnedDataValue) -> &'v OwnedDataValue {
383        self.index_into(value).unwrap_or(&OWNED_NULL)
384    }
385}
386
387impl<T: OwnedValueIndex + ?Sized> OwnedValueIndex for &T {
388    #[inline]
389    fn index_into<'v>(&self, value: &'v OwnedDataValue) -> Option<&'v OwnedDataValue> {
390        (**self).index_into(value)
391    }
392    #[inline]
393    fn index_into_or_null<'v>(&self, value: &'v OwnedDataValue) -> &'v OwnedDataValue {
394        (**self).index_into_or_null(value)
395    }
396}
397
398impl<I: OwnedValueIndex> Index<I> for OwnedDataValue {
399    type Output = OwnedDataValue;
400    #[inline]
401    fn index(&self, index: I) -> &OwnedDataValue {
402        index.index_into_or_null(self)
403    }
404}
405
406// ---- Convenience constructors ----
407
408impl OwnedDataValue {
409    #[inline]
410    pub fn from_i64(i: i64) -> Self {
411        OwnedDataValue::Number(NumberValue::Integer(i))
412    }
413    #[inline]
414    pub fn from_f64(f: f64) -> Self {
415        OwnedDataValue::Number(NumberValue::from_f64(f))
416    }
417
418    /// Build an `OwnedDataValue::Array` from anything iterable into values
419    /// that already convert into `OwnedDataValue` (covers all the existing
420    /// `From<bool|i64|f64|String|&str|...>` impls).
421    ///
422    /// ```
423    /// use datavalue_rs::OwnedDataValue;
424    /// let v = OwnedDataValue::array([1, 2, 3]);
425    /// assert_eq!(v[0].as_i64(), Some(1));
426    /// let v = OwnedDataValue::array(["a", "b"]);
427    /// assert_eq!(v[1].as_str(), Some("b"));
428    /// ```
429    pub fn array<I, V>(items: I) -> Self
430    where
431        I: IntoIterator<Item = V>,
432        V: Into<OwnedDataValue>,
433    {
434        OwnedDataValue::Array(items.into_iter().map(Into::into).collect())
435    }
436
437    /// Build an `OwnedDataValue::Object` from `(key, value)` pairs.
438    ///
439    /// ```
440    /// use datavalue_rs::OwnedDataValue;
441    /// let v = OwnedDataValue::object([("type", "NaN"), ("op", "+")]);
442    /// assert_eq!(v["type"].as_str(), Some("NaN"));
443    /// let v = OwnedDataValue::object([("count", 42)]);
444    /// assert_eq!(v["count"].as_i64(), Some(42));
445    /// ```
446    pub fn object<I, K, V>(pairs: I) -> Self
447    where
448        I: IntoIterator<Item = (K, V)>,
449        K: Into<String>,
450        V: Into<OwnedDataValue>,
451    {
452        OwnedDataValue::Object(
453            pairs
454                .into_iter()
455                .map(|(k, v)| (k.into(), v.into()))
456                .collect(),
457        )
458    }
459}
460
461impl From<Vec<(String, OwnedDataValue)>> for OwnedDataValue {
462    #[inline]
463    fn from(pairs: Vec<(String, OwnedDataValue)>) -> Self {
464        OwnedDataValue::Object(pairs)
465    }
466}
467
468impl From<bool> for OwnedDataValue {
469    #[inline]
470    fn from(b: bool) -> Self {
471        OwnedDataValue::Bool(b)
472    }
473}
474
475macro_rules! from_int {
476    ($($t:ty),*) => {$(
477        impl From<$t> for OwnedDataValue {
478            #[inline]
479            fn from(v: $t) -> Self { OwnedDataValue::from_i64(v as i64) }
480        }
481    )*};
482}
483from_int!(i8, i16, i32, i64, u8, u16, u32);
484
485impl From<u64> for OwnedDataValue {
486    /// Values up to `i64::MAX` stay on the integer path; larger values
487    /// fall back to `f64` (matches the parser / serde visitor behaviour).
488    #[inline]
489    fn from(v: u64) -> Self {
490        if v <= i64::MAX as u64 {
491            OwnedDataValue::from_i64(v as i64)
492        } else {
493            // Bypass `from_f64` (which would collapse this whole value back
494            // to Integer with i64 saturation) and construct Float directly.
495            OwnedDataValue::Number(NumberValue::Float(v as f64))
496        }
497    }
498}
499impl From<usize> for OwnedDataValue {
500    #[inline]
501    fn from(v: usize) -> Self {
502        OwnedDataValue::from(v as u64)
503    }
504}
505impl From<isize> for OwnedDataValue {
506    #[inline]
507    fn from(v: isize) -> Self {
508        OwnedDataValue::from_i64(v as i64)
509    }
510}
511
512impl From<f32> for OwnedDataValue {
513    #[inline]
514    fn from(v: f32) -> Self {
515        OwnedDataValue::from_f64(v as f64)
516    }
517}
518impl From<f64> for OwnedDataValue {
519    #[inline]
520    fn from(v: f64) -> Self {
521        OwnedDataValue::from_f64(v)
522    }
523}
524
525impl From<String> for OwnedDataValue {
526    #[inline]
527    fn from(s: String) -> Self {
528        OwnedDataValue::String(s)
529    }
530}
531impl From<&str> for OwnedDataValue {
532    #[inline]
533    fn from(s: &str) -> Self {
534        OwnedDataValue::String(s.to_string())
535    }
536}
537impl From<&String> for OwnedDataValue {
538    #[inline]
539    fn from(s: &String) -> Self {
540        OwnedDataValue::String(s.clone())
541    }
542}
543impl From<std::borrow::Cow<'_, str>> for OwnedDataValue {
544    #[inline]
545    fn from(s: std::borrow::Cow<'_, str>) -> Self {
546        OwnedDataValue::String(s.into_owned())
547    }
548}
549
550impl From<()> for OwnedDataValue {
551    #[inline]
552    fn from(_: ()) -> Self {
553        OwnedDataValue::Null
554    }
555}
556
557impl<T: Into<OwnedDataValue>> From<Option<T>> for OwnedDataValue {
558    #[inline]
559    fn from(opt: Option<T>) -> Self {
560        match opt {
561            Some(v) => v.into(),
562            None => OwnedDataValue::Null,
563        }
564    }
565}
566
567impl<T: Into<OwnedDataValue>> From<Vec<T>> for OwnedDataValue {
568    #[inline]
569    fn from(v: Vec<T>) -> Self {
570        OwnedDataValue::Array(v.into_iter().map(Into::into).collect())
571    }
572}
573
574impl<T: Into<OwnedDataValue> + Clone> From<&[T]> for OwnedDataValue {
575    #[inline]
576    fn from(v: &[T]) -> Self {
577        OwnedDataValue::Array(v.iter().cloned().map(Into::into).collect())
578    }
579}
580
581impl<T: Into<OwnedDataValue>, const N: usize> From<[T; N]> for OwnedDataValue {
582    #[inline]
583    fn from(v: [T; N]) -> Self {
584        OwnedDataValue::Array(v.into_iter().map(Into::into).collect())
585    }
586}
587
588impl<K: Into<String>, V: Into<OwnedDataValue>> From<std::collections::HashMap<K, V>>
589    for OwnedDataValue
590{
591    /// Note: `HashMap` iteration order is unspecified, so the resulting
592    /// `Object` has unspecified key order. Equality is by key set, so this
593    /// still round-trips correctly through `PartialEq`.
594    #[inline]
595    fn from(m: std::collections::HashMap<K, V>) -> Self {
596        OwnedDataValue::Object(m.into_iter().map(|(k, v)| (k.into(), v.into())).collect())
597    }
598}
599
600impl<K: Into<String>, V: Into<OwnedDataValue>> From<std::collections::BTreeMap<K, V>>
601    for OwnedDataValue
602{
603    #[inline]
604    fn from(m: std::collections::BTreeMap<K, V>) -> Self {
605        OwnedDataValue::Object(m.into_iter().map(|(k, v)| (k.into(), v.into())).collect())
606    }
607}
608
609#[cfg(test)]
610mod tests {
611    use super::*;
612
613    #[test]
614    fn parse_round_trip_via_owned() {
615        let v = OwnedDataValue::from_json(r#"{"a":1,"b":[true,null,"x"]}"#).unwrap();
616        assert_eq!(v["a"].as_i64(), Some(1));
617        assert_eq!(v["b"][0].as_bool(), Some(true));
618        assert!(v["b"][1].is_null());
619        assert_eq!(v["b"][2].as_str(), Some("x"));
620    }
621
622    #[test]
623    fn arena_to_owned_to_arena_round_trip() {
624        let arena = Bump::new();
625        let original =
626            DataValue::from_str(r#"{"x":42,"y":[1,2,3],"z":{"k":true}}"#, &arena).unwrap();
627        let owned = original.to_owned();
628
629        // Drop the arena; owned should still work.
630        drop(arena);
631        assert_eq!(owned["x"].as_i64(), Some(42));
632        assert_eq!(owned["y"][1].as_i64(), Some(2));
633        assert_eq!(owned["z"]["k"].as_bool(), Some(true));
634
635        // Rehydrate into a fresh arena and ensure equality on each side.
636        let arena2 = Bump::new();
637        let back = owned.to_arena(&arena2);
638        assert_eq!(back["x"].as_i64(), Some(42));
639        assert_eq!(back["y"][1].as_i64(), Some(2));
640        assert_eq!(back["z"]["k"].as_bool(), Some(true));
641
642        // And owned -> owned through an arena should equal the original.
643        assert_eq!(back.to_owned(), owned);
644    }
645
646    #[test]
647    fn missing_index_returns_null() {
648        let v = OwnedDataValue::from_json(r#"{"a":1}"#).unwrap();
649        assert!(v["missing"].is_null());
650        assert!(v["a"][99].is_null());
651    }
652
653    #[test]
654    fn equality_object_order_insensitive() {
655        let a = OwnedDataValue::Object(vec![
656            ("x".to_string(), OwnedDataValue::from_i64(1)),
657            ("y".to_string(), OwnedDataValue::from_i64(2)),
658        ]);
659        let b = OwnedDataValue::Object(vec![
660            ("y".to_string(), OwnedDataValue::from_i64(2)),
661            ("x".to_string(), OwnedDataValue::from_i64(1)),
662        ]);
663        assert_eq!(a, b);
664    }
665
666    #[cfg(feature = "datetime")]
667    #[test]
668    fn datetime_variant_round_trips_through_owned() {
669        use crate::datetime::DataDateTime;
670        let arena = Bump::new();
671        let dt = DataDateTime::parse("2024-01-15T12:30:45Z").unwrap();
672        let bv = DataValue::DateTime(dt);
673        let owned = bv.to_owned();
674        assert!(owned.is_datetime());
675        assert_eq!(
676            owned.as_datetime().unwrap().to_iso_string(),
677            "2024-01-15T12:30:45Z"
678        );
679        let back = owned.to_arena(&arena);
680        assert_eq!(back, bv);
681    }
682}