Skip to main content

datavalue_rs/
value.rs

1//! [`DataValue`] — bump-allocated JSON value type.
2//!
3//! Lifetime `'a` ties the value tree to a [`bumpalo::Bump`]. Composite
4//! variants (`String`, `Array`, `Object`) hold arena-allocated slices, so
5//! constructing a `DataValue` tree costs one or two arena bumps per node
6//! instead of a heap allocation per `Vec` / `BTreeMap` / `String`.
7
8use core::ops::Index;
9
10use bumpalo::Bump;
11
12#[cfg(feature = "datetime")]
13use crate::datetime::{DataDateTime, DataDuration};
14use crate::number::NumberValue;
15
16/// Arena-allocated JSON value tree. Mirrors `serde_json::Value` in shape
17/// and access surface, but every composite payload lives in a `Bump`.
18#[derive(Debug, Clone, Copy)]
19pub enum DataValue<'a> {
20    Null,
21    Bool(bool),
22    Number(NumberValue),
23    String(&'a str),
24    Array(&'a [DataValue<'a>]),
25    Object(&'a [(&'a str, DataValue<'a>)]),
26    /// UTC instant + original tz offset. JSON has no native datetime, so
27    /// the JSON parser never produces this — consumers upgrade from
28    /// `String` at the operator boundary.
29    #[cfg(feature = "datetime")]
30    DateTime(DataDateTime),
31    /// Signed duration. Same boundary rules as `DateTime`.
32    #[cfg(feature = "datetime")]
33    Duration(DataDuration),
34}
35
36/// Returned by `Index` impls when a key/index is missing — matches
37/// `serde_json::Value`'s "indexing returns Null on miss" behaviour.
38pub(crate) static NULL: DataValue<'static> = DataValue::Null;
39
40impl<'a> DataValue<'a> {
41    // ---- Constructors ----
42
43    #[inline]
44    pub fn null() -> Self {
45        DataValue::Null
46    }
47
48    #[inline]
49    pub fn bool(b: bool) -> Self {
50        DataValue::Bool(b)
51    }
52
53    #[inline]
54    pub fn from_i64(i: i64) -> Self {
55        DataValue::Number(NumberValue::from_i64(i))
56    }
57
58    #[inline]
59    pub fn from_f64(f: f64) -> Self {
60        DataValue::Number(NumberValue::from_f64(f))
61    }
62
63    #[inline]
64    pub fn from_str_in(s: &str, arena: &'a Bump) -> Self {
65        DataValue::String(arena.alloc_str(s))
66    }
67
68    /// Wrap a string slice that already lives in the arena (or has the
69    /// required lifetime). No allocation.
70    #[inline]
71    pub fn from_borrowed_str(s: &'a str) -> Self {
72        DataValue::String(s)
73    }
74
75    // ---- Type predicates ----
76
77    #[inline]
78    pub fn is_null(&self) -> bool {
79        matches!(self, DataValue::Null)
80    }
81    #[inline]
82    pub fn is_bool(&self) -> bool {
83        matches!(self, DataValue::Bool(_))
84    }
85    #[inline]
86    pub fn is_number(&self) -> bool {
87        matches!(self, DataValue::Number(_))
88    }
89    #[inline]
90    pub fn is_i64(&self) -> bool {
91        matches!(self, DataValue::Number(NumberValue::Integer(_)))
92    }
93    #[inline]
94    pub fn is_f64(&self) -> bool {
95        matches!(self, DataValue::Number(NumberValue::Float(_)))
96    }
97    #[inline]
98    pub fn is_string(&self) -> bool {
99        matches!(self, DataValue::String(_))
100    }
101    #[inline]
102    pub fn is_array(&self) -> bool {
103        matches!(self, DataValue::Array(_))
104    }
105    #[inline]
106    pub fn is_object(&self) -> bool {
107        matches!(self, DataValue::Object(_))
108    }
109
110    #[cfg(feature = "datetime")]
111    #[inline]
112    pub fn is_datetime(&self) -> bool {
113        matches!(self, DataValue::DateTime(_))
114    }
115    #[cfg(feature = "datetime")]
116    #[inline]
117    pub fn is_duration(&self) -> bool {
118        matches!(self, DataValue::Duration(_))
119    }
120
121    // ---- Accessors ----
122
123    #[inline]
124    pub fn as_bool(&self) -> Option<bool> {
125        match self {
126            DataValue::Bool(b) => Some(*b),
127            _ => None,
128        }
129    }
130
131    #[inline]
132    pub fn as_i64(&self) -> Option<i64> {
133        match self {
134            DataValue::Number(n) => n.as_i64(),
135            _ => None,
136        }
137    }
138
139    #[inline]
140    pub fn as_f64(&self) -> Option<f64> {
141        match self {
142            DataValue::Number(n) => Some(n.as_f64()),
143            _ => None,
144        }
145    }
146
147    #[inline]
148    pub fn as_number(&self) -> Option<&NumberValue> {
149        match self {
150            DataValue::Number(n) => Some(n),
151            _ => None,
152        }
153    }
154
155    #[inline]
156    pub fn as_str(&self) -> Option<&'a str> {
157        match *self {
158            DataValue::String(s) => Some(s),
159            _ => None,
160        }
161    }
162
163    #[inline]
164    pub fn as_array(&self) -> Option<&'a [DataValue<'a>]> {
165        match *self {
166            DataValue::Array(a) => Some(a),
167            _ => None,
168        }
169    }
170
171    #[inline]
172    pub fn as_object(&self) -> Option<&'a [(&'a str, DataValue<'a>)]> {
173        match *self {
174            DataValue::Object(o) => Some(o),
175            _ => None,
176        }
177    }
178
179    #[cfg(feature = "datetime")]
180    #[inline]
181    pub fn as_datetime(&self) -> Option<&DataDateTime> {
182        match self {
183            DataValue::DateTime(d) => Some(d),
184            _ => None,
185        }
186    }
187
188    #[cfg(feature = "datetime")]
189    #[inline]
190    pub fn as_duration(&self) -> Option<&DataDuration> {
191        match self {
192            DataValue::Duration(d) => Some(d),
193            _ => None,
194        }
195    }
196
197    #[cfg(feature = "datetime")]
198    #[inline]
199    pub fn datetime(dt: DataDateTime) -> Self {
200        DataValue::DateTime(dt)
201    }
202
203    #[cfg(feature = "datetime")]
204    #[inline]
205    pub fn duration(d: DataDuration) -> Self {
206        DataValue::Duration(d)
207    }
208
209    /// `serde_json::Value::get`-style lookup. Accepts `&str` for object
210    /// keys or `usize` for array indices.
211    #[inline]
212    pub fn get<I: ValueIndex>(&self, index: I) -> Option<&DataValue<'a>> {
213        I::index_into(&index, self)
214    }
215
216    /// Number of elements in an array / object. `None` for non-collections.
217    #[inline]
218    pub fn len(&self) -> Option<usize> {
219        match self {
220            DataValue::Array(a) => Some(a.len()),
221            DataValue::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 — same convenience pattern as `json-rust`'s `members`.
233    #[inline]
234    pub fn members(&self) -> core::slice::Iter<'_, DataValue<'a>> {
235        match *self {
236            DataValue::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) -> EntriesIter<'_, 'a> {
245        match *self {
246            DataValue::Object(pairs) => EntriesIter {
247                inner: pairs.iter(),
248            },
249            _ => EntriesIter { 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
262/// Iterator over `(key, value)` pairs in a [`DataValue::Object`]. Created
263/// via [`DataValue::entries`].
264pub struct EntriesIter<'v, 'a> {
265    inner: core::slice::Iter<'v, (&'a str, DataValue<'a>)>,
266}
267
268impl<'v, 'a> Iterator for EntriesIter<'v, 'a> {
269    type Item = (&'a str, &'v DataValue<'a>);
270    #[inline]
271    fn next(&mut self) -> Option<Self::Item> {
272        self.inner.next().map(|(k, v)| (*k, v))
273    }
274    #[inline]
275    fn size_hint(&self) -> (usize, Option<usize>) {
276        self.inner.size_hint()
277    }
278}
279
280impl ExactSizeIterator for EntriesIter<'_, '_> {}
281
282impl Default for DataValue<'_> {
283    #[inline]
284    fn default() -> Self {
285        DataValue::Null
286    }
287}
288
289impl<'a> PartialEq for DataValue<'a> {
290    #[inline]
291    fn eq(&self, other: &Self) -> bool {
292        match (self, other) {
293            (DataValue::Null, DataValue::Null) => true,
294            (DataValue::Bool(a), DataValue::Bool(b)) => a == b,
295            (DataValue::Number(a), DataValue::Number(b)) => a == b,
296            (DataValue::String(a), DataValue::String(b)) => a == b,
297            (DataValue::Array(a), DataValue::Array(b)) => a == b,
298            (DataValue::Object(a), DataValue::Object(b)) => {
299                if a.len() != b.len() {
300                    return false;
301                }
302                // Object equality is by key set, not key order — match serde_json.
303                a.iter().all(|(k, v)| {
304                    b.iter()
305                        .find(|(bk, _)| bk == k)
306                        .is_some_and(|(_, bv)| v == bv)
307                })
308            }
309            #[cfg(feature = "datetime")]
310            (DataValue::DateTime(a), DataValue::DateTime(b)) => a == b,
311            #[cfg(feature = "datetime")]
312            (DataValue::Duration(a), DataValue::Duration(b)) => a == b,
313            _ => false,
314        }
315    }
316}
317
318// ---- Index trait dispatch ----
319
320/// Sealed-style helper for `DataValue::get`. Implemented for `&str`,
321/// `String`, and `usize`.
322pub trait ValueIndex: private::Sealed {
323    fn index_into<'v, 'a>(&self, value: &'v DataValue<'a>) -> Option<&'v DataValue<'a>>;
324    fn index_into_or_null<'v, 'a>(&self, value: &'v DataValue<'a>) -> &'v DataValue<'a>;
325}
326
327mod private {
328    pub trait Sealed {}
329    impl Sealed for str {}
330    impl Sealed for String {}
331    impl Sealed for usize {}
332    impl<T: Sealed + ?Sized> Sealed for &T {}
333}
334
335impl ValueIndex for str {
336    #[inline]
337    fn index_into<'v, 'a>(&self, value: &'v DataValue<'a>) -> Option<&'v DataValue<'a>> {
338        match value {
339            DataValue::Object(pairs) => pairs.iter().find(|(k, _)| *k == self).map(|(_, v)| v),
340            _ => None,
341        }
342    }
343    #[inline]
344    fn index_into_or_null<'v, 'a>(&self, value: &'v DataValue<'a>) -> &'v DataValue<'a> {
345        // &NULL is &'static DataValue<'static>; covariance in 'a coerces it
346        // to &'v DataValue<'a> since 'static: 'a.
347        self.index_into(value).unwrap_or(&NULL)
348    }
349}
350
351impl ValueIndex for String {
352    #[inline]
353    fn index_into<'v, 'a>(&self, value: &'v DataValue<'a>) -> Option<&'v DataValue<'a>> {
354        self.as_str().index_into(value)
355    }
356    #[inline]
357    fn index_into_or_null<'v, 'a>(&self, value: &'v DataValue<'a>) -> &'v DataValue<'a> {
358        self.as_str().index_into_or_null(value)
359    }
360}
361
362impl ValueIndex for usize {
363    #[inline]
364    fn index_into<'v, 'a>(&self, value: &'v DataValue<'a>) -> Option<&'v DataValue<'a>> {
365        match value {
366            DataValue::Array(items) => items.get(*self),
367            _ => None,
368        }
369    }
370    #[inline]
371    fn index_into_or_null<'v, 'a>(&self, value: &'v DataValue<'a>) -> &'v DataValue<'a> {
372        self.index_into(value).unwrap_or(&NULL)
373    }
374}
375
376impl<T: ValueIndex + ?Sized> ValueIndex for &T {
377    #[inline]
378    fn index_into<'v, 'a>(&self, value: &'v DataValue<'a>) -> Option<&'v DataValue<'a>> {
379        (**self).index_into(value)
380    }
381    #[inline]
382    fn index_into_or_null<'v, 'a>(&self, value: &'v DataValue<'a>) -> &'v DataValue<'a> {
383        (**self).index_into_or_null(value)
384    }
385}
386
387impl<'a, I: ValueIndex> Index<I> for DataValue<'a> {
388    type Output = DataValue<'a>;
389    #[inline]
390    fn index(&self, index: I) -> &DataValue<'a> {
391        index.index_into_or_null(self)
392    }
393}
394
395#[cfg(test)]
396mod tests {
397    use super::*;
398
399    fn sample<'a>(arena: &'a Bump) -> DataValue<'a> {
400        let nested = arena.alloc_slice_copy(&[
401            DataValue::from_i64(1),
402            DataValue::from_i64(2),
403            DataValue::from_i64(3),
404        ]);
405        let inner_obj = arena.alloc_slice_copy(&[("k", DataValue::Bool(true))]);
406        let pairs = arena.alloc_slice_copy(&[
407            ("name", DataValue::from_borrowed_str("alice")),
408            ("nums", DataValue::Array(nested)),
409            ("inner", DataValue::Object(inner_obj)),
410        ]);
411        DataValue::Object(pairs)
412    }
413
414    #[test]
415    fn get_object_key() {
416        let arena = Bump::new();
417        let v = sample(&arena);
418        assert_eq!(v.get("name").and_then(|x| x.as_str()), Some("alice"));
419        assert!(v.get("missing").is_none());
420    }
421
422    #[test]
423    fn get_array_index() {
424        let arena = Bump::new();
425        let v = sample(&arena);
426        let nums = v.get("nums").unwrap();
427        assert_eq!(nums.get(0).and_then(|x| x.as_i64()), Some(1));
428        assert_eq!(nums.get(2).and_then(|x| x.as_i64()), Some(3));
429        assert!(nums.get(99).is_none());
430    }
431
432    #[test]
433    fn index_returns_null_for_missing() {
434        let arena = Bump::new();
435        let v = sample(&arena);
436        assert!(v["missing"].is_null());
437        assert!(v["nums"][99].is_null());
438    }
439
440    #[test]
441    fn chained_index() {
442        let arena = Bump::new();
443        let v = sample(&arena);
444        assert_eq!(v["inner"]["k"].as_bool(), Some(true));
445    }
446
447    #[test]
448    fn predicates_and_len() {
449        let arena = Bump::new();
450        let v = sample(&arena);
451        assert!(v.is_object());
452        assert_eq!(v.len(), Some(3));
453        assert_eq!(v["nums"].len(), Some(3));
454        assert_eq!(v["name"].len(), None);
455    }
456
457    #[cfg(feature = "datetime")]
458    #[test]
459    fn datetime_variant_round_trips_through_value() {
460        use crate::datetime::{DataDateTime, DataDuration};
461        let dt = DataDateTime::parse("2024-01-15T12:30:45Z").unwrap();
462        let v = DataValue::datetime(dt);
463        assert!(v.is_datetime());
464        assert_eq!(
465            v.as_datetime().map(|d| d.to_iso_string()).as_deref(),
466            Some("2024-01-15T12:30:45Z")
467        );
468
469        let dur = DataDuration::parse("1d:2h").unwrap();
470        let v2 = DataValue::duration(dur);
471        assert!(v2.is_duration());
472        assert_eq!(v2.as_duration().unwrap().to_string(), "1d:2h:0m:0s");
473
474        // Equality on the parent enum dispatches to the variant.
475        assert_eq!(v, DataValue::datetime(dt));
476        assert_ne!(v, v2);
477    }
478
479    #[test]
480    fn equality_object_order_insensitive() {
481        let arena = Bump::new();
482        let a =
483            arena.alloc_slice_copy(&[("x", DataValue::from_i64(1)), ("y", DataValue::from_i64(2))]);
484        let b =
485            arena.alloc_slice_copy(&[("y", DataValue::from_i64(2)), ("x", DataValue::from_i64(1))]);
486        assert_eq!(DataValue::Object(a), DataValue::Object(b));
487    }
488}