Skip to main content

azul_core/
json.rs

1//! JSON value types for C API (data definitions only, no serde_json dependency)
2//!
3//! The actual parsing/serialization lives in `azul_layout::json` which adds
4//! serde_json-based implementations on top of these types.
5
6use alloc::string::String;
7use alloc::vec::Vec;
8use azul_css::{
9    impl_option, impl_option_inner, impl_result, impl_result_inner, impl_vec, impl_vec_clone,
10    impl_vec_debug, impl_vec_mut, impl_vec_partialeq, AzString, OptionBool, OptionF64,
11    OptionString,
12};
13use core::fmt;
14
15// ============================================================================
16// JSON Value Type
17// ============================================================================
18
19/// A generic JSON value that can hold any JSON type
20#[derive(Debug, Clone, PartialEq)]
21#[repr(C)]
22pub struct Json {
23    /// The type of this JSON value
24    pub value_type: JsonType,
25    /// Internal storage - interpretation depends on `value_type`
26    /// For objects/arrays, this contains serialized data
27    pub internal: JsonInternal,
28}
29
30/// Internal storage for JSON values.
31///
32/// This is a C-FFI-compatible tagged-union-via-struct: all fields always exist,
33/// but only the field(s) corresponding to `JsonType` in the parent `Json` are
34/// meaningful.  For compound types (`Array`, `Object`) the serialized JSON is
35/// stored in `string_value` and re-parsed on each access — this trades repeated
36/// parsing cost for a flat, FFI-safe layout with no interior pointers.
37#[derive(Debug, Clone, PartialEq)]
38#[repr(C)]
39pub struct JsonInternal {
40    /// For strings and serialized objects/arrays
41    pub string_value: AzString,
42    /// For numbers
43    pub number_value: f64,
44    /// For booleans
45    pub bool_value: bool,
46}
47
48/// `Json::null()`. A default that is JSON null, rather than an empty string
49/// masquerading as a value.
50impl Default for Json {
51    fn default() -> Self {
52        Self::null()
53    }
54}
55
56impl Default for JsonInternal {
57    fn default() -> Self {
58        Self {
59            string_value: AzString::from(String::new()),
60            number_value: 0.0,
61            bool_value: false,
62        }
63    }
64}
65
66/// Type of a JSON value
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68#[repr(C)]
69pub enum JsonType {
70    /// JSON null
71    Null,
72    /// JSON boolean (true/false)
73    Bool,
74    /// JSON number (stored as f64)
75    Number,
76    /// JSON string
77    String,
78    /// JSON array
79    Array,
80    /// JSON object
81    Object,
82}
83
84/// Error when parsing JSON
85#[derive(Debug, Clone, PartialEq, Eq)]
86#[repr(C)]
87pub struct JsonParseError {
88    /// Error message
89    pub message: AzString,
90    /// Line number (if available)
91    pub line: u32,
92    /// Column number (if available)
93    pub column: u32,
94}
95
96impl fmt::Display for JsonParseError {
97    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98        if self.line > 0 {
99            write!(
100                f,
101                "{}:{}: {}",
102                self.line,
103                self.column,
104                self.message.as_str()
105            )
106        } else {
107            write!(f, "{}", self.message.as_str())
108        }
109    }
110}
111
112#[cfg(feature = "std")]
113impl std::error::Error for JsonParseError {}
114
115/// A key-value pair in a JSON object
116#[derive(Debug, Clone, PartialEq)]
117#[repr(C)]
118pub struct JsonKeyValue {
119    /// The key
120    pub key: AzString,
121    /// The value
122    pub value: Json,
123}
124
125impl JsonKeyValue {
126    /// Create a new key-value pair
127    #[must_use]
128    pub const fn create(key: AzString, value: Json) -> Self {
129        Self { key, value }
130    }
131}
132
133// ============================================================================
134// FFI-safe collection types
135// ============================================================================
136
137/// Option type for JsonKeyValue
138impl_option!(
139    JsonKeyValue,
140    OptionJsonKeyValue,
141    copy = false,
142    [Debug, Clone, PartialEq]
143);
144
145/// Vec of JsonKeyValue (FFI-safe)
146impl_vec!(
147    JsonKeyValue,
148    JsonKeyValueVec,
149    JsonKeyValueVecDestructor,
150    JsonKeyValueVecDestructorType,
151    JsonKeyValueVecSlice,
152    OptionJsonKeyValue
153);
154impl_vec_clone!(JsonKeyValue, JsonKeyValueVec, JsonKeyValueVecDestructor);
155impl_vec_debug!(JsonKeyValue, JsonKeyValueVec);
156
157impl JsonKeyValueVec {
158    /// Creates a new, heap-allocated `JsonKeyValueVec` by copying elements from a C array
159    #[inline]
160    #[allow(clippy::not_unsafe_ptr_arg_deref)]
161    // SAFETY/FFI: `*const T` is the C-ABI signature; the fn null-checks then derefs under the documented caller contract (C guarantees a valid ptr/len). Marking it `unsafe fn` would force unsafe blocks into the generated dll bindings.
162    #[must_use]
163    pub fn copy_from_array(ptr: *const JsonKeyValue, len: usize) -> Self {
164        if ptr.is_null() || len == 0 {
165            return Self::new();
166        }
167        let slice = unsafe { core::slice::from_raw_parts(ptr, len) };
168        Self::from_vec(slice.to_vec())
169    }
170}
171
172// FFI-safe JsonVec using impl_vec! macro
173impl_vec!(
174    Json,
175    JsonVec,
176    JsonVecDestructor,
177    JsonVecDestructorType,
178    JsonVecSlice,
179    OptionJson
180);
181impl_vec_clone!(Json, JsonVec, JsonVecDestructor);
182impl_vec_debug!(Json, JsonVec);
183impl_vec_partialeq!(Json, JsonVec);
184impl_vec_mut!(Json, JsonVec);
185
186impl JsonVec {
187    /// Creates a new, heap-allocated `JsonVec` by copying elements from a C array
188    #[inline]
189    #[allow(clippy::not_unsafe_ptr_arg_deref)]
190    // SAFETY/FFI: `*const T` is the C-ABI signature; the fn null-checks then derefs under the documented caller contract (C guarantees a valid ptr/len). Marking it `unsafe fn` would force unsafe blocks into the generated dll bindings.
191    #[must_use]
192    pub fn copy_from_array(ptr: *const Json, len: usize) -> Self {
193        if ptr.is_null() || len == 0 {
194            return Self::new();
195        }
196        let slice = unsafe { core::slice::from_raw_parts(ptr, len) };
197        Self::from_vec(slice.to_vec())
198    }
199}
200
201// FFI-safe Result type for JSON parsing
202impl_result!(
203    Json,
204    JsonParseError,
205    ResultJsonJsonParseError,
206    copy = false,
207    [Debug, Clone, PartialEq]
208);
209
210// FFI-safe Option types for JSON
211impl_option!(Json, OptionJson, copy = false, [Clone, Debug, PartialEq]);
212impl_option!(JsonVec, OptionJsonVec, copy = false, [Clone, Debug]);
213impl_option!(
214    JsonKeyValueVec,
215    OptionJsonKeyValueVec,
216    copy = false,
217    [Clone, Debug]
218);
219
220// FFI-safe Option types for JSON value extraction
221// Note: OptionBool and OptionF64 are already exported from azul_css
222impl_option!(
223    i64,
224    OptionI64,
225    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
226);
227
228// ============================================================================
229// Helpers
230// ============================================================================
231
232/// Try to losslessly convert an `f64` to `i64`.
233///
234/// Returns `Some` only when `n` is an integer that fits in `i64` without
235/// overflow.  The upper bound uses `< 2^63` (not `<= i64::MAX as f64`)
236/// because `i64::MAX` cannot be represented exactly in `f64` — the cast
237/// rounds up to `2^63`, which would cause overflow on `n as i64`.
238#[allow(clippy::cast_possible_truncation)] // bounded DPI/dimension/number conversion
239fn f64_as_i64(n: f64) -> Option<i64> {
240    if n.fract() == 0.0 && n >= -(2_f64.powi(63)) && n < 2_f64.powi(63) {
241        Some(n as i64)
242    } else {
243        None
244    }
245}
246
247// ============================================================================
248// Non-serde methods on Json (pure data, no parsing)
249// ============================================================================
250
251impl Json {
252    /// Create a null JSON value
253    #[must_use]
254    pub fn null() -> Self {
255        Self {
256            value_type: JsonType::Null,
257            internal: JsonInternal::default(),
258        }
259    }
260
261    /// Create a boolean JSON value
262    #[must_use]
263    pub fn bool(value: bool) -> Self {
264        Self {
265            value_type: JsonType::Bool,
266            internal: JsonInternal {
267                string_value: AzString::from(String::new()),
268                number_value: 0.0,
269                bool_value: value,
270            },
271        }
272    }
273
274    /// Create a number JSON value (floating-point)
275    #[must_use]
276    pub fn number(value: f64) -> Self {
277        Self {
278            value_type: JsonType::Number,
279            internal: JsonInternal {
280                string_value: AzString::from(String::new()),
281                number_value: value,
282                bool_value: false,
283            },
284        }
285    }
286
287    /// Create an integer JSON value.
288    ///
289    /// **Note:** the value is stored as `f64` internally, so `i64` values with
290    /// magnitude greater than 2^53 will lose precision silently.
291    #[allow(clippy::cast_precision_loss)] // bounded DPI/dimension/number conversion
292    #[must_use]
293    pub fn integer(value: i64) -> Self {
294        Self {
295            value_type: JsonType::Number,
296            internal: JsonInternal {
297                string_value: AzString::from(String::new()),
298                number_value: value as f64,
299                bool_value: false,
300            },
301        }
302    }
303
304    /// Create a string JSON value
305    pub fn string(value: impl Into<String>) -> Self {
306        Self {
307            value_type: JsonType::String,
308            internal: JsonInternal {
309                string_value: AzString::from(value.into()),
310                number_value: 0.0,
311                bool_value: false,
312            },
313        }
314    }
315
316    /// Check if this is null
317    #[must_use]
318    pub fn is_null(&self) -> bool {
319        self.value_type == JsonType::Null
320    }
321
322    /// Check if this is a boolean
323    #[must_use]
324    pub fn is_bool(&self) -> bool {
325        self.value_type == JsonType::Bool
326    }
327
328    /// Check if this is a number
329    #[must_use]
330    pub fn is_number(&self) -> bool {
331        self.value_type == JsonType::Number
332    }
333
334    /// Check if this is a string
335    #[must_use]
336    pub fn is_string(&self) -> bool {
337        self.value_type == JsonType::String
338    }
339
340    /// Check if this is an array
341    #[must_use]
342    pub fn is_array(&self) -> bool {
343        self.value_type == JsonType::Array
344    }
345
346    /// Check if this is an object
347    #[must_use]
348    pub fn is_object(&self) -> bool {
349        self.value_type == JsonType::Object
350    }
351
352    /// Get as boolean (returns None if not a bool)
353    #[must_use]
354    pub fn as_bool(&self) -> OptionBool {
355        if self.value_type == JsonType::Bool {
356            OptionBool::Some(self.internal.bool_value)
357        } else {
358            OptionBool::None
359        }
360    }
361
362    /// Get as number (returns None if not a number)
363    #[must_use]
364    pub fn as_number(&self) -> OptionF64 {
365        if self.value_type == JsonType::Number {
366            OptionF64::Some(self.internal.number_value)
367        } else {
368            OptionF64::None
369        }
370    }
371
372    /// Get as integer (returns None if not a number or not an integer)
373    #[must_use]
374    pub fn as_i64(&self) -> OptionI64 {
375        if self.value_type == JsonType::Number {
376            f64_as_i64(self.internal.number_value).map_or(OptionI64::None, OptionI64::Some)
377        } else {
378            OptionI64::None
379        }
380    }
381
382    /// Get as string (returns None if not a string)
383    #[must_use]
384    pub fn as_string(&self) -> OptionString {
385        if self.value_type == JsonType::String {
386            OptionString::Some(self.internal.string_value.clone())
387        } else {
388            OptionString::None
389        }
390    }
391
392    /// Get the raw internal string value (for arrays/objects this is the serialized JSON)
393    #[must_use]
394    pub fn raw_string(&self) -> &str {
395        self.internal.string_value.as_str()
396    }
397}
398
399/// Note: the `Display` output is meant for human-readable / debug display.
400/// String values are quoted but **not** JSON-escaped (no backslash escaping
401/// of embedded quotes, newlines, etc.).  Use `to_json_string()` (requires
402/// the `serde-json` feature) when valid JSON output is needed.
403impl fmt::Display for Json {
404    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
405        match self.value_type {
406            JsonType::Null => write!(f, "null"),
407            JsonType::Bool => write!(f, "{}", self.internal.bool_value),
408            JsonType::Number => {
409                let num = self.internal.number_value;
410                if let Some(i) = f64_as_i64(num) {
411                    write!(f, "{i}")
412                } else {
413                    write!(f, "{num}")
414                }
415            }
416            JsonType::String => write!(f, "\"{}\"", self.internal.string_value.as_str()),
417            JsonType::Array | JsonType::Object => {
418                write!(f, "{}", self.internal.string_value.as_str())
419            }
420        }
421    }
422}
423
424// ============================================================================
425// serde_json-dependent methods (gated behind "serde-json" feature)
426// ============================================================================
427
428#[cfg(feature = "serde-json")]
429impl serde::Serialize for Json {
430    /// Serialize as the JSON value this represents, not as its repr(C) fields.
431    ///
432    /// Without this, a struct holding a `Json` field cannot derive
433    /// `Serialize` at all — and the obvious workaround, storing the payload
434    /// as a `String` of JSON, produces escaped JSON-inside-JSON that every
435    /// consumer has to parse twice and nothing validates.
436    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
437        self.to_serde_value().serialize(s)
438    }
439}
440
441#[cfg(feature = "serde-json")]
442impl<'de> serde::Deserialize<'de> for Json {
443    fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
444        Ok(Self::from_serde_value(serde_json::Value::deserialize(d)?))
445    }
446}
447
448#[cfg(feature = "serde-json")]
449impl Json {
450    /// Parse JSON from a string.
451    ///
452    /// # Errors
453    ///
454    /// Returns [`JsonParseError`] (message plus line/column) if `s` is not
455    /// well-formed JSON.
456    pub fn parse(s: &str) -> Result<Self, JsonParseError> {
457        let value: serde_json::Value = serde_json::from_str(s).map_err(|e| JsonParseError {
458            message: AzString::from(alloc::format!("{e}")),
459            line: u32::try_from(e.line()).unwrap_or(u32::MAX),
460            column: u32::try_from(e.column()).unwrap_or(u32::MAX),
461        })?;
462        Ok(Self::from_serde_value(value))
463    }
464
465    /// Parse JSON from bytes (UTF-8).
466    ///
467    /// # Errors
468    ///
469    /// Returns [`JsonParseError`] (message plus line/column) if `bytes` is not
470    /// well-formed UTF-8 JSON.
471    pub fn parse_bytes(bytes: &[u8]) -> Result<Self, JsonParseError> {
472        let value: serde_json::Value =
473            serde_json::from_slice(bytes).map_err(|e| JsonParseError {
474                message: AzString::from(alloc::format!("{e}")),
475                line: u32::try_from(e.line()).unwrap_or(u32::MAX),
476                column: u32::try_from(e.column()).unwrap_or(u32::MAX),
477            })?;
478        Ok(Self::from_serde_value(value))
479    }
480
481    /// Convert from `serde_json::Value`
482    #[must_use]
483    pub fn from_serde_value(value: serde_json::Value) -> Self {
484        match value {
485            serde_json::Value::Null => Self::null(),
486            serde_json::Value::Bool(b) => Self::bool(b),
487            serde_json::Value::Number(n) => Self::number(n.as_f64().unwrap_or(0.0)),
488            serde_json::Value::String(s) => Self::string(s),
489            serde_json::Value::Array(arr) => {
490                let json_str =
491                    serde_json::to_string(&serde_json::Value::Array(arr)).unwrap_or_default();
492                Self {
493                    value_type: JsonType::Array,
494                    internal: JsonInternal {
495                        string_value: AzString::from(json_str),
496                        number_value: 0.0,
497                        bool_value: false,
498                    },
499                }
500            }
501            serde_json::Value::Object(obj) => {
502                let json_str =
503                    serde_json::to_string(&serde_json::Value::Object(obj)).unwrap_or_default();
504                Self {
505                    value_type: JsonType::Object,
506                    internal: JsonInternal {
507                        string_value: AzString::from(json_str),
508                        number_value: 0.0,
509                        bool_value: false,
510                    },
511                }
512            }
513        }
514    }
515
516    /// Convert this Json to a `serde_json::Value`
517    #[must_use]
518    pub fn to_serde_value(&self) -> serde_json::Value {
519        match self.value_type {
520            JsonType::Null => serde_json::Value::Null,
521            JsonType::Bool => serde_json::Value::Bool(self.internal.bool_value),
522            JsonType::Number => {
523                let num = self.internal.number_value;
524                f64_as_i64(num).map_or_else(
525                    || {
526                        serde_json::Number::from_f64(num)
527                            .map_or(serde_json::Value::Null, serde_json::Value::Number)
528                    },
529                    |i| serde_json::Value::Number(serde_json::Number::from(i)),
530                )
531            }
532            JsonType::String => {
533                serde_json::Value::String(self.internal.string_value.as_str().to_string())
534            }
535            JsonType::Array | JsonType::Object => {
536                serde_json::from_str(self.internal.string_value.as_str())
537                    .unwrap_or(serde_json::Value::Null)
538            }
539        }
540    }
541
542    /// Create a JSON array from a vector of JSON values
543    // By-value is the C-ABI shape: the generated bindings hand ownership in.
544    #[allow(clippy::needless_pass_by_value)]
545    #[must_use]
546    pub fn array(values: JsonVec) -> Self {
547        let serde_array: Vec<serde_json::Value> =
548            values.as_slice().iter().map(Self::to_serde_value).collect();
549        let json_str = serde_json::to_string(&serde_json::Value::Array(serde_array))
550            .unwrap_or_else(|_| "[]".to_string());
551        Self {
552            value_type: JsonType::Array,
553            internal: JsonInternal {
554                string_value: AzString::from(json_str),
555                number_value: 0.0,
556                bool_value: false,
557            },
558        }
559    }
560
561    /// Create a JSON object from key-value pairs
562    // By-value is the C-ABI shape: the generated bindings hand ownership in.
563    #[allow(clippy::needless_pass_by_value)]
564    #[must_use]
565    pub fn object(entries: JsonKeyValueVec) -> Self {
566        let mut map = serde_json::Map::new();
567        for kv in entries.as_slice() {
568            map.insert(kv.key.as_str().to_string(), kv.value.to_serde_value());
569        }
570        let json_str = serde_json::to_string(&serde_json::Value::Object(map))
571            .unwrap_or_else(|_| "{}".to_string());
572        Self {
573            value_type: JsonType::Object,
574            internal: JsonInternal {
575                string_value: AzString::from(json_str),
576                number_value: 0.0,
577                bool_value: false,
578            },
579        }
580    }
581
582    /// Get the number of elements (for arrays) or keys (for objects)
583    #[must_use]
584    pub fn len(&self) -> usize {
585        match self.value_type {
586            JsonType::Array => {
587                if let Ok(serde_json::Value::Array(arr)) =
588                    serde_json::from_str(self.internal.string_value.as_str())
589                {
590                    arr.len()
591                } else {
592                    0
593                }
594            }
595            JsonType::Object => {
596                if let Ok(serde_json::Value::Object(obj)) =
597                    serde_json::from_str(self.internal.string_value.as_str())
598                {
599                    obj.len()
600                } else {
601                    0
602                }
603            }
604            _ => 0,
605        }
606    }
607
608    /// Check if empty (for arrays/objects)
609    #[must_use]
610    pub fn is_empty(&self) -> bool {
611        self.len() == 0
612    }
613
614    /// Get array element by index
615    #[must_use]
616    pub fn get_index(&self, index: usize) -> Option<Self> {
617        if self.value_type != JsonType::Array {
618            return None;
619        }
620        let value: serde_json::Value =
621            serde_json::from_str(self.internal.string_value.as_str()).ok()?;
622        if let serde_json::Value::Array(arr) = value {
623            arr.get(index).map(|v| Self::from_serde_value(v.clone()))
624        } else {
625            None
626        }
627    }
628
629    /// Get object value by key
630    #[must_use]
631    pub fn get_key(&self, key: &str) -> Option<Self> {
632        if self.value_type != JsonType::Object {
633            return None;
634        }
635        let value: serde_json::Value =
636            serde_json::from_str(self.internal.string_value.as_str()).ok()?;
637        if let serde_json::Value::Object(obj) = value {
638            obj.get(key).map(|v| Self::from_serde_value(v.clone()))
639        } else {
640            None
641        }
642    }
643
644    /// Get all keys of an object
645    #[must_use]
646    pub fn keys(&self) -> Vec<AzString> {
647        if self.value_type != JsonType::Object {
648            return Vec::new();
649        }
650        let value: serde_json::Value =
651            match serde_json::from_str(self.internal.string_value.as_str()) {
652                Ok(v) => v,
653                Err(_) => return Vec::new(),
654            };
655        if let serde_json::Value::Object(obj) = value {
656            obj.keys().map(|k| AzString::from(k.clone())).collect()
657        } else {
658            Vec::new()
659        }
660    }
661
662    /// Convert array to Vec<Json>
663    pub fn to_array(&self) -> Option<JsonVec> {
664        if self.value_type != JsonType::Array {
665            return None;
666        }
667        let value: serde_json::Value =
668            serde_json::from_str(self.internal.string_value.as_str()).ok()?;
669        if let serde_json::Value::Array(arr) = value {
670            Some(arr.into_iter().map(Self::from_serde_value).collect())
671        } else {
672            None
673        }
674    }
675
676    /// Convert object to Vec<JsonKeyValue>
677    #[must_use]
678    pub fn to_object(&self) -> Option<JsonKeyValueVec> {
679        if self.value_type != JsonType::Object {
680            return None;
681        }
682        let value: serde_json::Value =
683            serde_json::from_str(self.internal.string_value.as_str()).ok()?;
684        if let serde_json::Value::Object(obj) = value {
685            Some(
686                obj.into_iter()
687                    .map(|(k, v)| JsonKeyValue {
688                        key: AzString::from(k),
689                        value: Self::from_serde_value(v),
690                    })
691                    .collect(),
692            )
693        } else {
694            None
695        }
696    }
697
698    /// Serialize to JSON string (returns `AzString`)
699    #[must_use]
700    pub fn to_json_string(&self) -> AzString {
701        match self.value_type {
702            JsonType::Null => AzString::from(alloc::string::String::from("null")),
703            JsonType::Bool => AzString::from(if self.internal.bool_value {
704                alloc::string::String::from("true")
705            } else {
706                alloc::string::String::from("false")
707            }),
708            JsonType::Number => {
709                let num = self.internal.number_value;
710                f64_as_i64(num).map_or_else(
711                    || AzString::from(alloc::format!("{num}")),
712                    |i| AzString::from(alloc::format!("{i}")),
713                )
714            }
715            JsonType::String => {
716                let escaped =
717                    serde_json::to_string(self.internal.string_value.as_str()).unwrap_or_default();
718                AzString::from(escaped)
719            }
720            JsonType::Array | JsonType::Object => self.internal.string_value.clone(),
721        }
722    }
723
724    /// Serialize to pretty-printed JSON string
725    #[must_use]
726    pub fn to_string_pretty(&self) -> AzString {
727        match self.value_type {
728            JsonType::Null | JsonType::Bool | JsonType::Number | JsonType::String => {
729                self.to_json_string()
730            }
731            JsonType::Array | JsonType::Object => {
732                serde_json::from_str::<serde_json::Value>(self.internal.string_value.as_str())
733                    .map_or_else(
734                        |_| self.internal.string_value.clone(),
735                        |value| {
736                            AzString::from(serde_json::to_string_pretty(&value).unwrap_or_default())
737                        },
738                    )
739            }
740        }
741    }
742
743    /// Access a nested value using a JSON Pointer (RFC 6901).
744    #[must_use]
745    pub fn jq(&self, path: &str) -> Self {
746        match self.value_type {
747            JsonType::Null | JsonType::Bool | JsonType::Number | JsonType::String => {
748                if path.is_empty() {
749                    self.clone()
750                } else {
751                    Self::null()
752                }
753            }
754            JsonType::Array | JsonType::Object => {
755                let value: serde_json::Value =
756                    match serde_json::from_str(self.internal.string_value.as_str()) {
757                        Ok(v) => v,
758                        Err(_) => return Self::null(),
759                    };
760                value
761                    .pointer(path)
762                    .map_or_else(Self::null, |v| Self::from_serde_value(v.clone()))
763            }
764        }
765    }
766
767    /// Access nested values using a JSON Pointer with wildcard support.
768    #[must_use]
769    pub fn jq_all(&self, path: &str) -> JsonVec {
770        let result = match self.value_type {
771            JsonType::Null | JsonType::Bool | JsonType::Number | JsonType::String => {
772                if path.is_empty() {
773                    vec![self.clone()]
774                } else {
775                    vec![]
776                }
777            }
778            JsonType::Array | JsonType::Object => {
779                let value: serde_json::Value =
780                    match serde_json::from_str(self.internal.string_value.as_str()) {
781                        Ok(v) => v,
782                        Err(_) => return JsonVec::from_vec(vec![]),
783                    };
784                Self::jq_all_recursive(&value, path)
785            }
786        };
787        JsonVec::from_vec(result)
788    }
789
790    /// Maximum JSON-Pointer component depth for [`jq_all`](Self::jq_all).
791    ///
792    /// AUDIT 2026-07-08: `jq_all_recursive` recursed once per pointer component,
793    /// so an attacker-supplied pointer with tens of thousands of `/` segments
794    /// (e.g. `"/a".repeat(100_000)`) overflowed the stack. The single-child
795    /// descent is now iterative (unbounded, allocation-free); only the wildcard
796    /// (`*`) fan-out still recurses, and that recursion is capped here. 512 is far
797    /// deeper than any real document nesting while staying well inside the stack.
798    const JQ_MAX_WILDCARD_DEPTH: usize = 512;
799
800    /// Recursive helper for `jq_all` that handles wildcards.
801    ///
802    /// Non-wildcard components are walked in a loop so a long linear pointer can
803    /// never overflow the stack; only `*` fan-out recurses, bounded by
804    /// [`JQ_MAX_WILDCARD_DEPTH`](Self::JQ_MAX_WILDCARD_DEPTH).
805    fn jq_all_recursive(value: &serde_json::Value, path: &str) -> Vec<Self> {
806        Self::jq_all_recursive_depth(value, path, 0)
807    }
808
809    fn jq_all_recursive_depth(value: &serde_json::Value, path: &str, depth: usize) -> Vec<Self> {
810        // Guard the wildcard recursion; exceeding the cap yields no match rather
811        // than crashing.
812        if depth > Self::JQ_MAX_WILDCARD_DEPTH {
813            return vec![];
814        }
815
816        // Walk non-wildcard components iteratively.
817        let mut value = value;
818        let mut path = path;
819        loop {
820            if path.is_empty() {
821                return vec![Self::from_serde_value(value.clone())];
822            }
823            if !path.starts_with('/') {
824                return vec![];
825            }
826            let rest = &path[1..];
827            let (component, remaining) = rest
828                .find('/')
829                .map_or((rest, ""), |idx| (&rest[..idx], &rest[idx..]));
830
831            if component == "*" {
832                let mut results = Vec::new();
833                match value {
834                    serde_json::Value::Array(arr) => {
835                        for item in arr {
836                            results.extend(Self::jq_all_recursive_depth(
837                                item,
838                                remaining,
839                                depth + 1,
840                            ));
841                        }
842                    }
843                    serde_json::Value::Object(obj) => {
844                        for (_key, val) in obj {
845                            results.extend(Self::jq_all_recursive_depth(val, remaining, depth + 1));
846                        }
847                    }
848                    _ => {}
849                }
850                return results;
851            }
852
853            // Single-child descent: advance the cursor instead of recursing.
854            let next = match value {
855                serde_json::Value::Array(arr) => {
856                    component.parse::<usize>().ok().and_then(|idx| arr.get(idx))
857                }
858                serde_json::Value::Object(obj) => obj.get(component),
859                _ => None,
860            };
861            match next {
862                Some(v) => {
863                    value = v;
864                    path = remaining;
865                }
866                None => return vec![],
867            }
868        }
869    }
870}
871
872#[cfg(test)]
873#[path = "json_test.rs"]
874mod json_test;