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