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