Skip to main content

jsonata_core/
value.rs

1// JValue: Rc-wrapped value type for O(1) cloning
2// Replaces serde_json::Value for internal use
3
4use std::fmt;
5use std::rc::Rc;
6
7use indexmap::IndexMap;
8use serde::de::{self, Deserializer, MapAccess, SeqAccess, Visitor};
9use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer};
10
11/// A JSON-like value with O(1) clone semantics via Rc-wrapping.
12///
13/// Standard JSON types (Array, Object, String) are wrapped in Rc for cheap cloning.
14/// Internal types (Undefined, Lambda, Builtin, Regex) are first-class variants
15/// instead of tagged objects with hash-map lookups.
16#[derive(Clone, Debug)]
17pub enum JValue {
18    // Standard JSON types
19    Null,
20    Bool(bool),
21    Number(f64),
22    String(Rc<str>),
23    Array(Rc<Vec<JValue>>),
24    Object(Rc<IndexMap<String, JValue>>),
25
26    // Internal types (previously tagged objects)
27    Undefined,
28    Lambda {
29        lambda_id: Rc<str>,
30        params: Rc<Vec<String>>,
31        name: Option<Rc<str>>,
32        signature: Option<Rc<str>>,
33    },
34    Builtin {
35        name: Rc<str>,
36    },
37    Regex {
38        pattern: Rc<str>,
39        flags: Rc<str>,
40    },
41}
42
43// ── Type checks ──────────────────────────────────────────────────────────────
44
45impl JValue {
46    #[inline]
47    pub fn is_null(&self) -> bool {
48        matches!(self, JValue::Null)
49    }
50
51    #[inline]
52    pub fn is_undefined(&self) -> bool {
53        matches!(self, JValue::Undefined)
54    }
55
56    #[inline]
57    pub fn is_bool(&self) -> bool {
58        matches!(self, JValue::Bool(_))
59    }
60
61    #[inline]
62    pub fn is_number(&self) -> bool {
63        matches!(self, JValue::Number(_))
64    }
65
66    #[inline]
67    pub fn is_string(&self) -> bool {
68        matches!(self, JValue::String(_))
69    }
70
71    #[inline]
72    pub fn is_array(&self) -> bool {
73        matches!(self, JValue::Array(_))
74    }
75
76    #[inline]
77    pub fn is_object(&self) -> bool {
78        matches!(self, JValue::Object(_))
79    }
80
81    #[inline]
82    pub fn is_lambda(&self) -> bool {
83        matches!(self, JValue::Lambda { .. })
84    }
85
86    #[inline]
87    pub fn is_builtin(&self) -> bool {
88        matches!(self, JValue::Builtin { .. })
89    }
90
91    #[inline]
92    pub fn is_function(&self) -> bool {
93        matches!(self, JValue::Lambda { .. } | JValue::Builtin { .. })
94    }
95
96    #[inline]
97    pub fn is_regex(&self) -> bool {
98        matches!(self, JValue::Regex { .. })
99    }
100}
101
102// ── Extraction ───────────────────────────────────────────────────────────────
103
104impl JValue {
105    #[inline]
106    pub fn as_f64(&self) -> Option<f64> {
107        match self {
108            JValue::Number(n) => Some(*n),
109            _ => None,
110        }
111    }
112
113    #[inline]
114    pub fn as_i64(&self) -> Option<i64> {
115        match self {
116            JValue::Number(n) => {
117                let f = *n;
118                if f.fract() == 0.0 && f >= i64::MIN as f64 && f <= i64::MAX as f64 {
119                    Some(f as i64)
120                } else {
121                    None
122                }
123            }
124            _ => None,
125        }
126    }
127
128    #[inline]
129    pub fn as_str(&self) -> Option<&str> {
130        match self {
131            JValue::String(s) => Some(s),
132            _ => None,
133        }
134    }
135
136    #[inline]
137    pub fn as_bool(&self) -> Option<bool> {
138        match self {
139            JValue::Bool(b) => Some(*b),
140            _ => None,
141        }
142    }
143
144    #[inline]
145    pub fn as_array(&self) -> Option<&Vec<JValue>> {
146        match self {
147            JValue::Array(arr) => Some(arr),
148            _ => None,
149        }
150    }
151
152    #[inline]
153    pub fn as_object(&self) -> Option<&IndexMap<String, JValue>> {
154        match self {
155            JValue::Object(map) => Some(map),
156            _ => None,
157        }
158    }
159
160    /// Get a mutable reference to the inner Vec, cloning if shared (Rc::make_mut).
161    #[inline]
162    pub fn as_array_mut(&mut self) -> Option<&mut Vec<JValue>> {
163        match self {
164            JValue::Array(arr) => Some(Rc::make_mut(arr)),
165            _ => None,
166        }
167    }
168
169    /// Get a mutable reference to the inner IndexMap, cloning if shared (Rc::make_mut).
170    #[inline]
171    pub fn as_object_mut(&mut self) -> Option<&mut IndexMap<String, JValue>> {
172        match self {
173            JValue::Object(map) => Some(Rc::make_mut(map)),
174            _ => None,
175        }
176    }
177
178    /// Get a reference to the Rc<str> for the string variant.
179    #[inline]
180    pub fn as_rc_str(&self) -> Option<&Rc<str>> {
181        match self {
182            JValue::String(s) => Some(s),
183            _ => None,
184        }
185    }
186
187    /// Index into an object by key.
188    #[inline]
189    pub fn get(&self, key: &str) -> Option<&JValue> {
190        match self {
191            JValue::Object(map) => map.get(key),
192            _ => None,
193        }
194    }
195
196    /// Index into an array by position.
197    #[inline]
198    pub fn get_index(&self, index: usize) -> Option<&JValue> {
199        match self {
200            JValue::Array(arr) => arr.get(index),
201            _ => None,
202        }
203    }
204}
205
206// ── Constructors ─────────────────────────────────────────────────────────────
207
208impl JValue {
209    #[inline]
210    pub fn from_i64(n: i64) -> Self {
211        JValue::Number(n as f64)
212    }
213
214    #[inline]
215    pub fn from_f64(n: f64) -> Self {
216        JValue::Number(n)
217    }
218
219    #[inline]
220    pub fn string(s: impl Into<Rc<str>>) -> Self {
221        JValue::String(s.into())
222    }
223
224    #[inline]
225    pub fn array(v: Vec<JValue>) -> Self {
226        JValue::Array(Rc::new(v))
227    }
228
229    #[inline]
230    pub fn object(m: IndexMap<String, JValue>) -> Self {
231        JValue::Object(Rc::new(m))
232    }
233
234    #[inline]
235    pub fn lambda(
236        lambda_id: impl Into<Rc<str>>,
237        params: Vec<String>,
238        name: Option<impl Into<Rc<str>>>,
239        signature: Option<impl Into<Rc<str>>>,
240    ) -> Self {
241        JValue::Lambda {
242            lambda_id: lambda_id.into(),
243            params: Rc::new(params),
244            name: name.map(|n| n.into()),
245            signature: signature.map(|s| s.into()),
246        }
247    }
248
249    #[inline]
250    pub fn builtin(name: impl Into<Rc<str>>) -> Self {
251        JValue::Builtin { name: name.into() }
252    }
253
254    #[inline]
255    pub fn regex(pattern: impl Into<Rc<str>>, flags: impl Into<Rc<str>>) -> Self {
256        JValue::Regex {
257            pattern: pattern.into(),
258            flags: flags.into(),
259        }
260    }
261}
262
263// ── From impls ───────────────────────────────────────────────────────────────
264
265impl From<bool> for JValue {
266    #[inline]
267    fn from(b: bool) -> Self {
268        JValue::Bool(b)
269    }
270}
271
272impl From<i64> for JValue {
273    #[inline]
274    fn from(n: i64) -> Self {
275        JValue::Number(n as f64)
276    }
277}
278
279impl From<i32> for JValue {
280    #[inline]
281    fn from(n: i32) -> Self {
282        JValue::Number(n as f64)
283    }
284}
285
286impl From<u64> for JValue {
287    #[inline]
288    fn from(n: u64) -> Self {
289        JValue::Number(n as f64)
290    }
291}
292
293impl From<usize> for JValue {
294    #[inline]
295    fn from(n: usize) -> Self {
296        JValue::Number(n as f64)
297    }
298}
299
300impl From<f64> for JValue {
301    #[inline]
302    fn from(n: f64) -> Self {
303        JValue::Number(n)
304    }
305}
306
307impl From<&str> for JValue {
308    #[inline]
309    fn from(s: &str) -> Self {
310        JValue::String(s.into())
311    }
312}
313
314impl From<String> for JValue {
315    #[inline]
316    fn from(s: String) -> Self {
317        JValue::String(s.into())
318    }
319}
320
321impl From<Rc<str>> for JValue {
322    #[inline]
323    fn from(s: Rc<str>) -> Self {
324        JValue::String(s)
325    }
326}
327
328impl From<Vec<JValue>> for JValue {
329    #[inline]
330    fn from(v: Vec<JValue>) -> Self {
331        JValue::Array(Rc::new(v))
332    }
333}
334
335impl From<IndexMap<String, JValue>> for JValue {
336    #[inline]
337    fn from(m: IndexMap<String, JValue>) -> Self {
338        JValue::Object(Rc::new(m))
339    }
340}
341
342// ── PartialEq ────────────────────────────────────────────────────────────────
343
344impl PartialEq for JValue {
345    fn eq(&self, other: &Self) -> bool {
346        match (self, other) {
347            (JValue::Null, JValue::Null) => true,
348            (JValue::Undefined, JValue::Undefined) => true,
349            (JValue::Bool(a), JValue::Bool(b)) => a == b,
350            (JValue::Number(a), JValue::Number(b)) => {
351                // Handle NaN: NaN != NaN
352                if a.is_nan() && b.is_nan() {
353                    return false;
354                }
355                a == b
356            }
357            (JValue::String(a), JValue::String(b)) => a == b,
358            (JValue::Array(a), JValue::Array(b)) => a == b,
359            (JValue::Object(a), JValue::Object(b)) => a == b,
360            (JValue::Lambda { lambda_id: a, .. }, JValue::Lambda { lambda_id: b, .. }) => a == b,
361            (JValue::Builtin { name: a }, JValue::Builtin { name: b }) => a == b,
362            (
363                JValue::Regex {
364                    pattern: ap,
365                    flags: af,
366                },
367                JValue::Regex {
368                    pattern: bp,
369                    flags: bf,
370                },
371            ) => ap == bp && af == bf,
372            _ => false,
373        }
374    }
375}
376
377// ── Display ──────────────────────────────────────────────────────────────────
378
379impl fmt::Display for JValue {
380    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
381        match self {
382            JValue::Null => write!(f, "null"),
383            JValue::Undefined => write!(f, "undefined"),
384            JValue::Bool(b) => write!(f, "{}", b),
385            JValue::Number(n) => format_number(*n, f),
386            JValue::String(s) => write!(f, "\"{}\"", escape_json_string(s)),
387            JValue::Array(arr) => {
388                write!(f, "[")?;
389                for (i, v) in arr.iter().enumerate() {
390                    if i > 0 {
391                        write!(f, ",")?;
392                    }
393                    write!(f, "{}", v)?;
394                }
395                write!(f, "]")
396            }
397            JValue::Object(map) => {
398                write!(f, "{{")?;
399                for (i, (k, v)) in map.iter().enumerate() {
400                    if i > 0 {
401                        write!(f, ",")?;
402                    }
403                    write!(f, "\"{}\":{}", escape_json_string(k), v)?;
404                }
405                write!(f, "}}")
406            }
407            JValue::Lambda { lambda_id, .. } => write!(f, "\"<lambda:{}>\"", lambda_id),
408            JValue::Builtin { name } => write!(f, "\"<builtin:{}>\"", name),
409            JValue::Regex { pattern, flags } => write!(f, "\"<regex:/{}/{}>\"", pattern, flags),
410        }
411    }
412}
413
414fn escape_json_string(s: &str) -> String {
415    let mut result = String::with_capacity(s.len());
416    for c in s.chars() {
417        match c {
418            '"' => result.push_str("\\\""),
419            '\\' => result.push_str("\\\\"),
420            '\n' => result.push_str("\\n"),
421            '\r' => result.push_str("\\r"),
422            '\t' => result.push_str("\\t"),
423            c if c < '\x20' => {
424                result.push_str(&format!("\\u{:04x}", c as u32));
425            }
426            c => result.push(c),
427        }
428    }
429    result
430}
431
432fn format_number(n: f64, f: &mut fmt::Formatter<'_>) -> fmt::Result {
433    if !n.is_finite() {
434        // NaN and +/-Infinity serialize as null (matching JSON spec)
435        write!(f, "null")
436    } else if n.fract() == 0.0 && n.abs() < 1e20 {
437        write!(f, "{}", n as i64)
438    } else {
439        // Use serde_json's number formatting for consistency
440        write!(f, "{}", n)
441    }
442}
443
444// ── Serialization (for JSON output via evaluate_json) ────────────────────────
445
446impl Serialize for JValue {
447    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
448    where
449        S: Serializer,
450    {
451        match self {
452            JValue::Null => serializer.serialize_none(),
453            JValue::Undefined => serializer.serialize_none(),
454            JValue::Bool(b) => serializer.serialize_bool(*b),
455            JValue::Number(n) => {
456                if n.is_nan() || n.is_infinite() {
457                    serializer.serialize_none()
458                } else if n.fract() == 0.0 && *n >= i64::MIN as f64 && *n <= i64::MAX as f64 {
459                    serializer.serialize_i64(*n as i64)
460                } else {
461                    serializer.serialize_f64(*n)
462                }
463            }
464            JValue::String(s) => serializer.serialize_str(s),
465            JValue::Array(arr) => {
466                let mut seq = serializer.serialize_seq(Some(arr.len()))?;
467                for v in arr.iter() {
468                    seq.serialize_element(v)?;
469                }
470                seq.end()
471            }
472            JValue::Object(map) => {
473                let mut m = serializer.serialize_map(Some(map.len()))?;
474                for (k, v) in map.iter() {
475                    m.serialize_entry(k, v)?;
476                }
477                m.end()
478            }
479            JValue::Lambda { .. } => serializer.serialize_str(""),
480            JValue::Builtin { .. } => serializer.serialize_str(""),
481            JValue::Regex { pattern, flags } => {
482                let mut m = serializer.serialize_map(Some(2))?;
483                m.serialize_entry("pattern", &**pattern)?;
484                m.serialize_entry("flags", &**flags)?;
485                m.end()
486            }
487        }
488    }
489}
490
491// ── Deserialization (single-pass JSON→JValue) ────────────────────────────────
492
493impl<'de> serde::Deserialize<'de> for JValue {
494    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
495    where
496        D: Deserializer<'de>,
497    {
498        deserializer.deserialize_any(JValueVisitor)
499    }
500}
501
502struct JValueVisitor;
503
504impl<'de> Visitor<'de> for JValueVisitor {
505    type Value = JValue;
506
507    fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
508        write!(f, "any valid JSON value")
509    }
510
511    fn visit_bool<E: de::Error>(self, v: bool) -> Result<JValue, E> {
512        Ok(JValue::Bool(v))
513    }
514
515    fn visit_i64<E: de::Error>(self, v: i64) -> Result<JValue, E> {
516        Ok(JValue::Number(v as f64))
517    }
518
519    fn visit_u64<E: de::Error>(self, v: u64) -> Result<JValue, E> {
520        Ok(JValue::Number(v as f64))
521    }
522
523    fn visit_f64<E: de::Error>(self, v: f64) -> Result<JValue, E> {
524        Ok(JValue::Number(v))
525    }
526
527    fn visit_str<E: de::Error>(self, v: &str) -> Result<JValue, E> {
528        Ok(JValue::string(v))
529    }
530
531    fn visit_string<E: de::Error>(self, v: String) -> Result<JValue, E> {
532        Ok(JValue::String(v.into()))
533    }
534
535    fn visit_none<E: de::Error>(self) -> Result<JValue, E> {
536        Ok(JValue::Null)
537    }
538
539    fn visit_unit<E: de::Error>(self) -> Result<JValue, E> {
540        Ok(JValue::Null)
541    }
542
543    fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<JValue, A::Error> {
544        let mut vec = Vec::with_capacity(seq.size_hint().unwrap_or(0));
545        while let Some(elem) = seq.next_element()? {
546            vec.push(elem);
547        }
548        Ok(JValue::array(vec))
549    }
550
551    fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<JValue, A::Error> {
552        let mut m = IndexMap::with_capacity(map.size_hint().unwrap_or(0));
553        while let Some((k, v)) = map.next_entry()? {
554            m.insert(k, v);
555        }
556        Ok(JValue::object(m))
557    }
558}
559
560// ── JSON string I/O ──────────────────────────────────────────────────────────
561
562impl JValue {
563    /// Serialize to a JSON string.
564    pub fn to_json_string(&self) -> Result<String, serde_json::Error> {
565        serde_json::to_string(self)
566    }
567
568    /// Serialize to a pretty-printed JSON string.
569    pub fn to_json_string_pretty(&self) -> Result<String, serde_json::Error> {
570        serde_json::to_string_pretty(self)
571    }
572
573    /// Parse a JSON string into a JValue (single-pass, no intermediate serde_json::Value).
574    ///
575    /// When the `simd` feature is enabled, uses simd-json for faster parsing on CPUs
576    /// with SIMD support (SSE4.2/AVX2/NEON). Falls back to serde_json otherwise.
577    ///
578    /// `simd_json::serde::from_slice` allocates fresh internal scratch buffers
579    /// (`Buffers::new`) on every call, sized proportionally to the input - measured
580    /// as the dominant cost, large enough to make the "simd" path consistently
581    /// SLOWER than plain serde_json (worse as input grows: -26% at 189 bytes,
582    /// -29% at 180KB). Reusing those buffers across calls via
583    /// `from_slice_with_buffers` (thread-local, since JValue's Rc-based design is
584    /// already single-threaded per instance) eliminates that reallocation and
585    /// brings simd-json back to consistently faster than serde_json (+5% to +22%
586    /// across the same size range, measured with benches/examples/simd_json_bench.rs).
587    pub fn from_json_str(s: &str) -> Result<JValue, serde_json::Error> {
588        #[cfg(feature = "simd")]
589        {
590            thread_local! {
591                static SCRATCH: std::cell::RefCell<(Vec<u8>, simd_json::Buffers)> =
592                    std::cell::RefCell::new((Vec::new(), simd_json::Buffers::new(0)));
593            }
594
595            let result = SCRATCH.with(|scratch| {
596                let (bytes, buffers) = &mut *scratch.borrow_mut();
597                bytes.clear();
598                bytes.extend_from_slice(s.as_bytes());
599                simd_json::serde::from_slice_with_buffers::<JValue>(bytes, buffers)
600            });
601            if let Ok(value) = result {
602                return Ok(value);
603            }
604            // Fall back to serde_json if simd-json fails (e.g., unsupported CPU)
605        }
606        serde_json::from_str(s)
607    }
608}
609
610// ── Conversion from serde_json::Value ────────────────────────────────────────
611
612impl From<serde_json::Value> for JValue {
613    fn from(v: serde_json::Value) -> Self {
614        match v {
615            serde_json::Value::Null => JValue::Null,
616            serde_json::Value::Bool(b) => JValue::Bool(b),
617            serde_json::Value::Number(n) => JValue::Number(n.as_f64().unwrap_or(0.0)),
618            serde_json::Value::String(s) => JValue::String(s.into()),
619            serde_json::Value::Array(arr) => {
620                JValue::Array(Rc::new(arr.into_iter().map(JValue::from).collect()))
621            }
622            serde_json::Value::Object(map) => {
623                let m: IndexMap<String, JValue> =
624                    map.into_iter().map(|(k, v)| (k, JValue::from(v))).collect();
625                JValue::Object(Rc::new(m))
626            }
627        }
628    }
629}
630
631// ── Conversion to serde_json::Value (for Python boundary) ────────────────────
632
633impl From<&JValue> for serde_json::Value {
634    fn from(v: &JValue) -> Self {
635        match v {
636            JValue::Null | JValue::Undefined => serde_json::Value::Null,
637            JValue::Bool(b) => serde_json::Value::Bool(*b),
638            JValue::Number(n) => {
639                if n.is_nan() || n.is_infinite() {
640                    serde_json::Value::Null
641                } else {
642                    serde_json::json!(*n)
643                }
644            }
645            JValue::String(s) => serde_json::Value::String(s.to_string()),
646            JValue::Array(arr) => {
647                serde_json::Value::Array(arr.iter().map(serde_json::Value::from).collect())
648            }
649            JValue::Object(map) => {
650                let m: serde_json::Map<String, serde_json::Value> = map
651                    .iter()
652                    .map(|(k, v)| (k.clone(), serde_json::Value::from(v)))
653                    .collect();
654                serde_json::Value::Object(m)
655            }
656            JValue::Lambda { .. } | JValue::Builtin { .. } => serde_json::Value::Null,
657            JValue::Regex { pattern, flags } => {
658                let mut m = serde_json::Map::new();
659                m.insert(
660                    "pattern".to_string(),
661                    serde_json::Value::String(pattern.to_string()),
662                );
663                m.insert(
664                    "flags".to_string(),
665                    serde_json::Value::String(flags.to_string()),
666                );
667                serde_json::Value::Object(m)
668            }
669        }
670    }
671}
672
673// ── jvalue! macro ────────────────────────────────────────────────────────────
674
675/// Macro for constructing JValue literals, similar to serde_json::json!
676///
677/// Usage:
678///   jvalue!(null)           → JValue::Null
679///   jvalue!(true)           → JValue::Bool(true)
680///   jvalue!(false)          → JValue::Bool(false)
681///   jvalue!(42)             → JValue::Number(42.0)
682///   jvalue!(3.14)           → JValue::Number(3.14)
683///   jvalue!("hello")        → JValue::String(Rc::from("hello"))
684///   jvalue!([1, 2, 3])      → JValue::Array(Rc::new(vec![...]))
685///   jvalue!({"k": v, ...})  → JValue::Object(Rc::new(IndexMap from pairs))
686///   jvalue!(expr)           → JValue::from(expr)
687#[macro_export]
688macro_rules! jvalue {
689    // null
690    (null) => {
691        $crate::value::JValue::Null
692    };
693
694    // true
695    (true) => {
696        $crate::value::JValue::Bool(true)
697    };
698
699    // false
700    (false) => {
701        $crate::value::JValue::Bool(false)
702    };
703
704    // Array
705    ([ $($elem:tt),* $(,)? ]) => {
706        $crate::value::JValue::Array(std::rc::Rc::new(vec![ $( jvalue!($elem) ),* ]))
707    };
708
709    // Object
710    ({ $($key:tt : $val:tt),* $(,)? }) => {
711        {
712            let mut map = indexmap::IndexMap::new();
713            $(
714                map.insert(($key).to_string(), jvalue!($val));
715            )*
716            $crate::value::JValue::Object(std::rc::Rc::new(map))
717        }
718    };
719
720    // Expression (fallback — numbers, variables, function calls, etc.)
721    ($other:expr) => {
722        $crate::value::JValue::from($other)
723    };
724}
725
726// ── Tests ────────────────────────────────────────────────────────────────────
727
728#[cfg(test)]
729mod tests {
730    use super::*;
731
732    #[test]
733    fn test_clone_is_cheap() {
734        // Array clone should be O(1) — same Rc pointer
735        let arr = JValue::array(vec![
736            JValue::from(1i64),
737            JValue::from(2i64),
738            JValue::from(3i64),
739        ]);
740        let arr2 = arr.clone();
741        if let (JValue::Array(a), JValue::Array(b)) = (&arr, &arr2) {
742            assert!(Rc::ptr_eq(a, b));
743        } else {
744            panic!("expected arrays");
745        }
746
747        // Object clone should be O(1)
748        let mut map = IndexMap::new();
749        map.insert("x".to_string(), JValue::from(1i64));
750        let obj = JValue::object(map);
751        let obj2 = obj.clone();
752        if let (JValue::Object(a), JValue::Object(b)) = (&obj, &obj2) {
753            assert!(Rc::ptr_eq(a, b));
754        } else {
755            panic!("expected objects");
756        }
757
758        // String clone should be O(1)
759        let s = JValue::string("hello");
760        let s2 = s.clone();
761        if let (JValue::String(a), JValue::String(b)) = (&s, &s2) {
762            assert!(Rc::ptr_eq(a, b));
763        } else {
764            panic!("expected strings");
765        }
766    }
767
768    #[test]
769    fn test_type_checks() {
770        assert!(JValue::Null.is_null());
771        assert!(JValue::Undefined.is_undefined());
772        assert!(JValue::Bool(true).is_bool());
773        assert!(JValue::Number(42.0).is_number());
774        assert!(JValue::string("hello").is_string());
775        assert!(JValue::array(vec![]).is_array());
776        assert!(JValue::object(IndexMap::new()).is_object());
777        assert!(JValue::lambda("id", vec![], None::<&str>, None::<&str>).is_lambda());
778        assert!(JValue::lambda("id", vec![], None::<&str>, None::<&str>).is_function());
779        assert!(JValue::builtin("sum").is_builtin());
780        assert!(JValue::builtin("sum").is_function());
781        assert!(JValue::regex(".*", "i").is_regex());
782    }
783
784    #[test]
785    fn test_extraction() {
786        assert_eq!(JValue::Number(42.0).as_f64(), Some(42.0));
787        assert_eq!(JValue::Number(42.0).as_i64(), Some(42));
788        assert_eq!(JValue::Number(42.5).as_i64(), None);
789        assert_eq!(JValue::string("hello").as_str(), Some("hello"));
790        assert_eq!(JValue::Bool(true).as_bool(), Some(true));
791        assert_eq!(
792            JValue::array(vec![JValue::from(1i64)])
793                .as_array()
794                .map(|a| a.len()),
795            Some(1)
796        );
797    }
798
799    #[test]
800    fn test_jvalue_macro() {
801        let n = jvalue!(null);
802        assert!(n.is_null());
803
804        let b = jvalue!(true);
805        assert_eq!(b.as_bool(), Some(true));
806
807        let arr = jvalue!([1i64, 2i64, 3i64]);
808        assert_eq!(arr.as_array().map(|a| a.len()), Some(3));
809
810        let obj = jvalue!({"name": "Alice", "age": 30i64});
811        assert_eq!(obj.get("name").and_then(|v| v.as_str()), Some("Alice"));
812    }
813
814    #[test]
815    fn test_equality() {
816        assert_eq!(JValue::Null, JValue::Null);
817        assert_eq!(JValue::Bool(true), JValue::Bool(true));
818        assert_ne!(JValue::Bool(true), JValue::Bool(false));
819        assert_eq!(JValue::Number(42.0), JValue::Number(42.0));
820        assert_ne!(JValue::Number(f64::NAN), JValue::Number(f64::NAN));
821        assert_eq!(JValue::string("hello"), JValue::string("hello"));
822        assert_ne!(JValue::Null, JValue::Undefined);
823    }
824
825    #[test]
826    fn test_serde_roundtrip() {
827        let v = jvalue!({"name": "Alice", "scores": [1i64, 2i64, 3i64], "active": true});
828        let json_str = v.to_json_string().unwrap();
829        let parsed = JValue::from_json_str(&json_str).unwrap();
830        assert_eq!(v, parsed);
831    }
832
833    #[test]
834    fn test_from_serde_json() {
835        let sv = serde_json::json!({"name": "Alice", "age": 30, "scores": [1, 2, 3]});
836        let jv = JValue::from(sv);
837        assert_eq!(jv.get("name").and_then(|v| v.as_str()), Some("Alice"));
838        assert_eq!(jv.get("age").and_then(|v| v.as_f64()), Some(30.0));
839    }
840
841    #[test]
842    fn test_make_mut() {
843        let mut arr = JValue::array(vec![JValue::from(1i64), JValue::from(2i64)]);
844        let arr2 = arr.clone();
845
846        // Mutate arr — should CoW (clone-on-write)
847        arr.as_array_mut().unwrap().push(JValue::from(3i64));
848
849        // arr2 should be unchanged
850        assert_eq!(arr.as_array().unwrap().len(), 3);
851        assert_eq!(arr2.as_array().unwrap().len(), 2);
852    }
853}