Skip to main content

oxiphysics_io/
json_io.rs

1#![allow(clippy::manual_strip, clippy::should_implement_trait)]
2// Copyright 2026 COOLJAPAN OU (Team KitaSan)
3// SPDX-License-Identifier: Apache-2.0
4
5//! Simple hand-rolled JSON serialization for physics data (no serde dependency).
6
7#![allow(dead_code)]
8
9/// A JSON value that covers all standard JSON types.
10#[derive(Debug, Clone, PartialEq)]
11pub enum JsonValue {
12    /// JSON `null`.
13    Null,
14    /// JSON boolean.
15    Bool(bool),
16    /// JSON number (stored as f64).
17    Number(f64),
18    /// JSON string.
19    Str(String),
20    /// JSON array.
21    Array(Vec<JsonValue>),
22    /// JSON object as ordered key-value pairs.
23    Object(Vec<(String, JsonValue)>),
24}
25
26impl JsonValue {
27    /// Serialize this value to a JSON string.
28    pub fn to_json_string(&self) -> String {
29        match self {
30            JsonValue::Null => "null".to_string(),
31            JsonValue::Bool(b) => b.to_string(),
32            JsonValue::Number(n) => {
33                if n.is_nan() {
34                    "null".to_string()
35                } else if n.is_infinite() {
36                    if *n > 0.0 {
37                        "1e308".to_string()
38                    } else {
39                        "-1e308".to_string()
40                    }
41                } else if n.fract() == 0.0 && n.abs() < 1e15 {
42                    format!("{}", *n as i64)
43                } else {
44                    format!("{}", n)
45                }
46            }
47            JsonValue::Str(s) => {
48                let escaped = s
49                    .replace('\\', "\\\\")
50                    .replace('"', "\\\"")
51                    .replace('\n', "\\n")
52                    .replace('\r', "\\r")
53                    .replace('\t', "\\t");
54                format!("\"{}\"", escaped)
55            }
56            JsonValue::Array(arr) => {
57                let parts: Vec<String> = arr.iter().map(|v| v.to_json_string()).collect();
58                format!("[{}]", parts.join(","))
59            }
60            JsonValue::Object(pairs) => {
61                let parts: Vec<String> = pairs
62                    .iter()
63                    .map(|(k, v)| format!("\"{}\":{}", k, v.to_json_string()))
64                    .collect();
65                format!("{{{}}}", parts.join(","))
66            }
67        }
68    }
69
70    /// Serialize to a pretty-printed JSON string with indentation.
71    pub fn to_json_pretty(&self, indent: usize) -> String {
72        self.to_json_pretty_inner(indent, 0)
73    }
74
75    fn to_json_pretty_inner(&self, indent: usize, depth: usize) -> String {
76        let pad = " ".repeat(indent * depth);
77        let pad_inner = " ".repeat(indent * (depth + 1));
78        match self {
79            JsonValue::Null | JsonValue::Bool(_) | JsonValue::Number(_) | JsonValue::Str(_) => {
80                self.to_json_string()
81            }
82            JsonValue::Array(arr) => {
83                if arr.is_empty() {
84                    return "[]".to_string();
85                }
86                let parts: Vec<String> = arr
87                    .iter()
88                    .map(|v| format!("{}{}", pad_inner, v.to_json_pretty_inner(indent, depth + 1)))
89                    .collect();
90                format!("[\n{}\n{}]", parts.join(",\n"), pad)
91            }
92            JsonValue::Object(pairs) => {
93                if pairs.is_empty() {
94                    return "{}".to_string();
95                }
96                let parts: Vec<String> = pairs
97                    .iter()
98                    .map(|(k, v)| {
99                        format!(
100                            "{}\"{}\": {}",
101                            pad_inner,
102                            k,
103                            v.to_json_pretty_inner(indent, depth + 1)
104                        )
105                    })
106                    .collect();
107                format!("{{\n{}\n{}}}", parts.join(",\n"), pad)
108            }
109        }
110    }
111
112    /// Parse a JSON string into a `JsonValue`.
113    pub fn from_str(s: &str) -> Result<Self, String> {
114        let s = s.trim();
115        let (val, rest) = parse_value(s)?;
116        if rest.trim().is_empty() {
117            Ok(val)
118        } else {
119            Err(format!("Unexpected trailing input: {:?}", rest.trim()))
120        }
121    }
122
123    /// If this is a `Number`, return its `f64` value.
124    pub fn as_f64(&self) -> Option<f64> {
125        if let JsonValue::Number(n) = self {
126            Some(*n)
127        } else {
128            None
129        }
130    }
131
132    /// If this is a `Bool`, return its value.
133    pub fn as_bool(&self) -> Option<bool> {
134        if let JsonValue::Bool(b) = self {
135            Some(*b)
136        } else {
137            None
138        }
139    }
140
141    /// If this is a `Str`, return a reference to the inner string.
142    pub fn as_str(&self) -> Option<&str> {
143        if let JsonValue::Str(s) = self {
144            Some(s.as_str())
145        } else {
146            None
147        }
148    }
149
150    /// If this is an `Array`, return a reference to the inner `Vec`.
151    pub fn as_array(&self) -> Option<&Vec<JsonValue>> {
152        if let JsonValue::Array(arr) = self {
153            Some(arr)
154        } else {
155            None
156        }
157    }
158
159    /// If this is an `Object`, return a reference to the inner pairs.
160    pub fn as_object(&self) -> Option<&Vec<(String, JsonValue)>> {
161        if let JsonValue::Object(pairs) = self {
162            Some(pairs)
163        } else {
164            None
165        }
166    }
167
168    /// Look up a key in an `Object`.  Returns `None` if not an object or key absent.
169    pub fn get(&self, key: &str) -> Option<&JsonValue> {
170        if let JsonValue::Object(pairs) = self {
171            pairs.iter().find(|(k, _)| k == key).map(|(_, v)| v)
172        } else {
173            None
174        }
175    }
176
177    /// Index into an `Array`.  Returns `None` if not an array or index out of bounds.
178    pub fn get_index(&self, i: usize) -> Option<&JsonValue> {
179        if let JsonValue::Array(arr) = self {
180            arr.get(i)
181        } else {
182            None
183        }
184    }
185
186    /// Navigate a dot-separated path like "a.b.c" through nested objects.
187    pub fn get_path(&self, path: &str) -> Option<&JsonValue> {
188        let mut current = self;
189        for key in path.split('.') {
190            current = current.get(key)?;
191        }
192        Some(current)
193    }
194
195    /// Check if this value is null.
196    pub fn is_null(&self) -> bool {
197        matches!(self, JsonValue::Null)
198    }
199
200    /// Return the number of elements in an array or object, or 0 otherwise.
201    pub fn len(&self) -> usize {
202        match self {
203            JsonValue::Array(arr) => arr.len(),
204            JsonValue::Object(pairs) => pairs.len(),
205            _ => 0,
206        }
207    }
208
209    /// Return true if this value is an empty array, empty object, or null.
210    pub fn is_empty(&self) -> bool {
211        match self {
212            JsonValue::Null => true,
213            JsonValue::Array(arr) => arr.is_empty(),
214            JsonValue::Object(pairs) => pairs.is_empty(),
215            _ => false,
216        }
217    }
218
219    /// Merge two JSON objects. Keys from `other` override keys in `self`.
220    /// Non-object values return `other`.
221    pub fn merge(&self, other: &JsonValue) -> JsonValue {
222        match (self, other) {
223            (JsonValue::Object(base), JsonValue::Object(overlay)) => {
224                let mut merged = base.clone();
225                for (key, val) in overlay {
226                    if let Some(pos) = merged.iter().position(|(k, _)| k == key) {
227                        merged[pos] = (key.clone(), merged[pos].1.merge(val));
228                    } else {
229                        merged.push((key.clone(), val.clone()));
230                    }
231                }
232                JsonValue::Object(merged)
233            }
234            _ => other.clone(),
235        }
236    }
237
238    /// Return all keys if this is an object.
239    pub fn keys(&self) -> Vec<&str> {
240        if let JsonValue::Object(pairs) = self {
241            pairs.iter().map(|(k, _)| k.as_str()).collect()
242        } else {
243            Vec::new()
244        }
245    }
246
247    /// Return all values if this is an object.
248    pub fn values(&self) -> Vec<&JsonValue> {
249        if let JsonValue::Object(pairs) = self {
250            pairs.iter().map(|(_, v)| v).collect()
251        } else {
252            Vec::new()
253        }
254    }
255}
256
257// ─── JSON Schema Validation ──────────────────────────────────────────────────
258
259/// Simple JSON schema for validation.
260#[derive(Debug, Clone)]
261pub enum JsonSchema {
262    /// Any value is accepted.
263    Any,
264    /// Must be null.
265    Null,
266    /// Must be a boolean.
267    Bool,
268    /// Must be a number.
269    Number,
270    /// Must be a string.
271    StringType,
272    /// Must be an array where each element matches the inner schema.
273    ArrayOf(Box<JsonSchema>),
274    /// Must be an object with the specified required fields.
275    ObjectWith {
276        /// Required fields: (key, schema).
277        required: Vec<(String, JsonSchema)>,
278        /// Whether additional fields are allowed.
279        allow_extra: bool,
280    },
281}
282
283impl JsonSchema {
284    /// Validate a JSON value against this schema. Returns Ok(()) or an error message.
285    pub fn validate(&self, value: &JsonValue) -> Result<(), String> {
286        match self {
287            JsonSchema::Any => Ok(()),
288            JsonSchema::Null => {
289                if value.is_null() {
290                    Ok(())
291                } else {
292                    Err("expected null".to_string())
293                }
294            }
295            JsonSchema::Bool => {
296                if value.as_bool().is_some() {
297                    Ok(())
298                } else {
299                    Err("expected boolean".to_string())
300                }
301            }
302            JsonSchema::Number => {
303                if value.as_f64().is_some() {
304                    Ok(())
305                } else {
306                    Err("expected number".to_string())
307                }
308            }
309            JsonSchema::StringType => {
310                if value.as_str().is_some() {
311                    Ok(())
312                } else {
313                    Err("expected string".to_string())
314                }
315            }
316            JsonSchema::ArrayOf(inner) => {
317                let arr = value.as_array().ok_or("expected array")?;
318                for (i, elem) in arr.iter().enumerate() {
319                    inner
320                        .validate(elem)
321                        .map_err(|e| format!("array[{i}]: {e}"))?;
322                }
323                Ok(())
324            }
325            JsonSchema::ObjectWith {
326                required,
327                allow_extra,
328            } => {
329                let pairs = value.as_object().ok_or("expected object")?;
330                for (key, schema) in required {
331                    let val = value
332                        .get(key)
333                        .ok_or_else(|| format!("missing required key '{key}'"))?;
334                    schema
335                        .validate(val)
336                        .map_err(|e| format!("key '{key}': {e}"))?;
337                }
338                if !allow_extra {
339                    let allowed: std::collections::HashSet<&str> =
340                        required.iter().map(|(k, _)| k.as_str()).collect();
341                    for (key, _) in pairs {
342                        if !allowed.contains(key.as_str()) {
343                            return Err(format!("unexpected key '{key}'"));
344                        }
345                    }
346                }
347                Ok(())
348            }
349        }
350    }
351}
352
353// ─── JSON Diff ───────────────────────────────────────────────────────────────
354
355/// A single difference between two JSON values.
356#[derive(Debug, Clone, PartialEq)]
357pub struct JsonDiff {
358    /// Dot-separated path to the differing value.
359    pub path: String,
360    /// The value in the left document (None if absent).
361    pub left: Option<JsonValue>,
362    /// The value in the right document (None if absent).
363    pub right: Option<JsonValue>,
364}
365
366/// Compute the differences between two JSON values.
367pub fn json_diff(left: &JsonValue, right: &JsonValue) -> Vec<JsonDiff> {
368    let mut diffs = Vec::new();
369    json_diff_inner(left, right, String::new(), &mut diffs);
370    diffs
371}
372
373fn json_diff_inner(left: &JsonValue, right: &JsonValue, path: String, diffs: &mut Vec<JsonDiff>) {
374    if left == right {
375        return;
376    }
377    match (left, right) {
378        (JsonValue::Object(lp), JsonValue::Object(rp)) => {
379            let mut seen = std::collections::HashSet::new();
380            for (k, lv) in lp {
381                seen.insert(k.clone());
382                let child_path = if path.is_empty() {
383                    k.clone()
384                } else {
385                    format!("{}.{}", path, k)
386                };
387                if let Some(rv) = rp.iter().find(|(rk, _)| rk == k).map(|(_, v)| v) {
388                    json_diff_inner(lv, rv, child_path, diffs);
389                } else {
390                    diffs.push(JsonDiff {
391                        path: child_path,
392                        left: Some(lv.clone()),
393                        right: None,
394                    });
395                }
396            }
397            for (k, rv) in rp {
398                if !seen.contains(k) {
399                    let child_path = if path.is_empty() {
400                        k.clone()
401                    } else {
402                        format!("{}.{}", path, k)
403                    };
404                    diffs.push(JsonDiff {
405                        path: child_path,
406                        left: None,
407                        right: Some(rv.clone()),
408                    });
409                }
410            }
411        }
412        (JsonValue::Array(la), JsonValue::Array(ra)) => {
413            let max_len = la.len().max(ra.len());
414            for i in 0..max_len {
415                let child_path = if path.is_empty() {
416                    format!("[{i}]")
417                } else {
418                    format!("{path}[{i}]")
419                };
420                match (la.get(i), ra.get(i)) {
421                    (Some(lv), Some(rv)) => json_diff_inner(lv, rv, child_path, diffs),
422                    (Some(lv), None) => diffs.push(JsonDiff {
423                        path: child_path,
424                        left: Some(lv.clone()),
425                        right: None,
426                    }),
427                    (None, Some(rv)) => diffs.push(JsonDiff {
428                        path: child_path,
429                        left: None,
430                        right: Some(rv.clone()),
431                    }),
432                    (None, None) => {}
433                }
434            }
435        }
436        _ => {
437            diffs.push(JsonDiff {
438                path: if path.is_empty() {
439                    "$".to_string()
440                } else {
441                    path
442                },
443                left: Some(left.clone()),
444                right: Some(right.clone()),
445            });
446        }
447    }
448}
449
450// ─── JSON Streaming Parser ───────────────────────────────────────────────────
451
452/// A simple streaming JSON parser that processes a JSON array element by element.
453///
454/// Feeds chunks of text and emits complete JSON values as they are parsed.
455pub struct JsonStreamParser {
456    buffer: String,
457    results: Vec<JsonValue>,
458}
459
460impl JsonStreamParser {
461    /// Create a new streaming parser.
462    pub fn new() -> Self {
463        Self {
464            buffer: String::new(),
465            results: Vec::new(),
466        }
467    }
468
469    /// Feed a chunk of text to the parser.
470    /// Returns the number of complete values parsed from this chunk.
471    pub fn feed(&mut self, chunk: &str) -> usize {
472        self.buffer.push_str(chunk);
473        let mut count = 0;
474        loop {
475            let trimmed = self.buffer.trim_start();
476            if trimmed.is_empty() {
477                break;
478            }
479            // Skip leading commas, brackets for array streaming
480            let first = trimmed.as_bytes()[0];
481            if first == b'[' || first == b',' {
482                self.buffer = trimmed[1..].to_string();
483                continue;
484            }
485            if first == b']' {
486                self.buffer = trimmed[1..].to_string();
487                continue;
488            }
489            match parse_value(trimmed) {
490                Ok((val, rest)) => {
491                    self.results.push(val);
492                    self.buffer = rest.to_string();
493                    count += 1;
494                }
495                Err(_) => break, // Incomplete data, wait for more
496            }
497        }
498        count
499    }
500
501    /// Take all parsed values, leaving the internal store empty.
502    pub fn take_results(&mut self) -> Vec<JsonValue> {
503        std::mem::take(&mut self.results)
504    }
505
506    /// Number of complete values parsed so far.
507    pub fn result_count(&self) -> usize {
508        self.results.len()
509    }
510}
511impl Default for JsonStreamParser {
512    fn default() -> Self {
513        Self::new()
514    }
515}
516
517// ─── Records helper ──────────────────────────────────────────────────────────
518
519/// Convert a slice of records (each a Vec of key-value pairs) to a JSON array.
520pub fn records_to_json(records: &[Vec<(String, JsonValue)>]) -> JsonValue {
521    let arr: Vec<JsonValue> = records
522        .iter()
523        .map(|rec| JsonValue::Object(rec.clone()))
524        .collect();
525    JsonValue::Array(arr)
526}
527
528/// Extract records from a JSON array of objects.
529pub fn json_to_records(value: &JsonValue) -> Option<Vec<Vec<(String, JsonValue)>>> {
530    let arr = value.as_array()?;
531    let mut records = Vec::new();
532    for item in arr {
533        let pairs = item.as_object()?;
534        records.push(pairs.clone());
535    }
536    Some(records)
537}
538
539// ─── Parser helpers ──────────────────────────────────────────────────────────
540
541fn skip_whitespace(s: &str) -> &str {
542    s.trim_start_matches(|c: char| c.is_ascii_whitespace())
543}
544
545fn parse_value(s: &str) -> Result<(JsonValue, &str), String> {
546    let s = skip_whitespace(s);
547    if s.is_empty() {
548        return Err("Unexpected end of input".to_string());
549    }
550    match s.as_bytes()[0] {
551        b'n' => parse_null(s),
552        b't' | b'f' => parse_bool(s),
553        b'"' => parse_string(s),
554        b'[' => parse_array(s),
555        b'{' => parse_object(s),
556        b'-' | b'0'..=b'9' => parse_number(s),
557        c => Err(format!("Unexpected character: '{}'", c as char)),
558    }
559}
560
561fn parse_null(s: &str) -> Result<(JsonValue, &str), String> {
562    s.strip_prefix("null")
563        .map(|rest| (JsonValue::Null, rest))
564        .ok_or_else(|| format!("Expected 'null', got: {:?}", &s[..s.len().min(6)]))
565}
566
567fn parse_bool(s: &str) -> Result<(JsonValue, &str), String> {
568    if let Some(rest) = s.strip_prefix("true") {
569        return Ok((JsonValue::Bool(true), rest));
570    }
571    if let Some(rest) = s.strip_prefix("false") {
572        return Ok((JsonValue::Bool(false), rest));
573    }
574    Err(format!("Expected boolean, got: {:?}", &s[..s.len().min(6)]))
575}
576
577fn parse_number(s: &str) -> Result<(JsonValue, &str), String> {
578    let end = s
579        .find(|c: char| !matches!(c, '-' | '+' | '.' | 'e' | 'E' | '0'..='9'))
580        .unwrap_or(s.len());
581    let num_str = &s[..end];
582    let n: f64 = num_str
583        .parse()
584        .map_err(|e| format!("Number parse error '{}': {}", num_str, e))?;
585    Ok((JsonValue::Number(n), &s[end..]))
586}
587
588fn parse_string(s: &str) -> Result<(JsonValue, &str), String> {
589    // s must start with '"'
590    let s = &s[1..]; // skip opening quote
591    let mut result = String::new();
592    let mut chars = s.char_indices();
593    loop {
594        match chars.next() {
595            None => return Err("Unterminated string".to_string()),
596            Some((_, '"')) => {
597                // Find byte position after the closing quote
598                let pos = s.len() - chars.as_str().len();
599                return Ok((JsonValue::Str(result), &s[pos..]));
600            }
601            Some((_, '\\')) => match chars.next() {
602                Some((_, '"')) => result.push('"'),
603                Some((_, '\\')) => result.push('\\'),
604                Some((_, '/')) => result.push('/'),
605                Some((_, 'n')) => result.push('\n'),
606                Some((_, 'r')) => result.push('\r'),
607                Some((_, 't')) => result.push('\t'),
608                Some((_, 'b')) => result.push('\x08'),
609                Some((_, 'f')) => result.push('\x0C'),
610                Some((_, 'u')) => {
611                    // Basic \uXXXX support
612                    let hex: String = (0..4)
613                        .map(|_| chars.next().map(|(_, c)| c).unwrap_or('0'))
614                        .collect();
615                    let code = u32::from_str_radix(&hex, 16)
616                        .map_err(|_| format!("Invalid unicode escape \\u{}", hex))?;
617                    let ch = char::from_u32(code)
618                        .ok_or_else(|| format!("Invalid unicode codepoint U+{:04X}", code))?;
619                    result.push(ch);
620                }
621                Some((_, c)) => return Err(format!("Invalid escape \\{}", c)),
622                None => return Err("Unterminated escape".to_string()),
623            },
624            Some((_, c)) => result.push(c),
625        }
626    }
627}
628
629fn parse_array(s: &str) -> Result<(JsonValue, &str), String> {
630    // s starts with '['
631    let mut s = skip_whitespace(&s[1..]);
632    let mut items = Vec::new();
633    if s.starts_with(']') {
634        return Ok((JsonValue::Array(items), &s[1..]));
635    }
636    loop {
637        let (val, rest) = parse_value(s)?;
638        items.push(val);
639        s = skip_whitespace(rest);
640        if s.starts_with(']') {
641            return Ok((JsonValue::Array(items), &s[1..]));
642        }
643        if s.starts_with(',') {
644            s = skip_whitespace(&s[1..]);
645        } else {
646            return Err(format!(
647                "Expected ',' or ']' in array, got: {:?}",
648                &s[..s.len().min(5)]
649            ));
650        }
651    }
652}
653
654fn parse_object(s: &str) -> Result<(JsonValue, &str), String> {
655    // s starts with '{'
656    let mut s = skip_whitespace(&s[1..]);
657    let mut pairs = Vec::new();
658    if s.starts_with('}') {
659        return Ok((JsonValue::Object(pairs), &s[1..]));
660    }
661    loop {
662        s = skip_whitespace(s);
663        // Parse key
664        if !s.starts_with('"') {
665            return Err(format!(
666                "Expected string key in object, got: {:?}",
667                &s[..s.len().min(5)]
668            ));
669        }
670        let (key_val, rest) = parse_string(s)?;
671        let key = match key_val {
672            JsonValue::Str(k) => k,
673            _ => unreachable!(),
674        };
675        s = skip_whitespace(rest);
676        if !s.starts_with(':') {
677            return Err(format!(
678                "Expected ':' after key, got: {:?}",
679                &s[..s.len().min(5)]
680            ));
681        }
682        s = skip_whitespace(&s[1..]);
683        let (val, rest) = parse_value(s)?;
684        pairs.push((key, val));
685        s = skip_whitespace(rest);
686        if s.starts_with('}') {
687            return Ok((JsonValue::Object(pairs), &s[1..]));
688        }
689        if s.starts_with(',') {
690            s = skip_whitespace(&s[1..]);
691        } else {
692            return Err(format!(
693                "Expected ',' or '}}' in object, got: {:?}",
694                &s[..s.len().min(5)]
695            ));
696        }
697    }
698}
699
700// ─── Physics helpers ─────────────────────────────────────────────────────────
701
702/// Serialize a 3D vector `[x, y, z]` to a JSON array.
703pub fn vec3_to_json(v: [f64; 3]) -> JsonValue {
704    JsonValue::Array(v.iter().copied().map(JsonValue::Number).collect())
705}
706
707/// Deserialize a JSON array of 3 numbers into `[f64; 3]`.
708pub fn json_to_vec3(j: &JsonValue) -> Option<[f64; 3]> {
709    let arr = j.as_array()?;
710    if arr.len() < 3 {
711        return None;
712    }
713    Some([arr[0].as_f64()?, arr[1].as_f64()?, arr[2].as_f64()?])
714}
715
716/// Serialize a matrix (as nested array) to JSON.
717pub fn matrix_to_json(rows: &[Vec<f64>]) -> JsonValue {
718    JsonValue::Array(
719        rows.iter()
720            .map(|row| JsonValue::Array(row.iter().copied().map(JsonValue::Number).collect()))
721            .collect(),
722    )
723}
724
725/// Build a JSON object `{"particles": [...]}` where each element has
726/// `"position"` and `"velocity"` arrays.
727pub fn particles_to_json(positions: &[[f64; 3]], velocities: &[[f64; 3]]) -> JsonValue {
728    let particles: Vec<JsonValue> = positions
729        .iter()
730        .zip(velocities.iter())
731        .map(|(pos, vel)| {
732            JsonValue::Object(vec![
733                ("position".to_string(), vec3_to_json(*pos)),
734                ("velocity".to_string(), vec3_to_json(*vel)),
735            ])
736        })
737        .collect();
738    JsonValue::Object(vec![("particles".to_string(), JsonValue::Array(particles))])
739}
740
741// ─── JSON Streaming Writer ────────────────────────────────────────────────────
742
743/// A streaming JSON writer that builds a JSON document incrementally.
744///
745/// Supports arrays and objects.  Call `begin_array`/`end_array` or
746/// `begin_object`/`end_object` to open/close containers, then
747/// `write_value` to add values.
748pub struct JsonStreamingWriter {
749    buffer: String,
750    /// Stack of (is_array, needs_comma) for each open container.
751    stack: Vec<(bool, bool)>,
752}
753
754impl JsonStreamingWriter {
755    /// Create a new streaming writer.
756    pub fn new() -> Self {
757        Self {
758            buffer: String::new(),
759            stack: Vec::new(),
760        }
761    }
762
763    /// Begin a JSON array `[`.
764    pub fn begin_array(&mut self) {
765        self.maybe_comma();
766        self.buffer.push('[');
767        self.stack.push((true, false));
768    }
769
770    /// End a JSON array `]`.
771    pub fn end_array(&mut self) {
772        self.buffer.push(']');
773        self.stack.pop();
774        if let Some(top) = self.stack.last_mut() {
775            top.1 = true; // next sibling needs comma
776        }
777    }
778
779    /// Begin a JSON object `{`.
780    pub fn begin_object(&mut self) {
781        self.maybe_comma();
782        self.buffer.push('{');
783        self.stack.push((false, false));
784    }
785
786    /// End a JSON object `}`.
787    pub fn end_object(&mut self) {
788        self.buffer.push('}');
789        self.stack.pop();
790        if let Some(top) = self.stack.last_mut() {
791            top.1 = true;
792        }
793    }
794
795    /// Write an object key (must be inside an object).
796    pub fn write_key(&mut self, key: &str) {
797        self.maybe_comma();
798        let escaped = key.replace('\\', "\\\\").replace('"', "\\\"");
799        self.buffer.push_str(&format!("\"{}\":", escaped));
800        // After writing a key, the next value should NOT be preceded by a comma
801        if let Some(top) = self.stack.last_mut() {
802            top.1 = false;
803        }
804    }
805
806    /// Write a JSON value.
807    pub fn write_value(&mut self, value: &JsonValue) {
808        self.maybe_comma();
809        self.buffer.push_str(&value.to_json_string());
810        if let Some(top) = self.stack.last_mut() {
811            top.1 = true;
812        }
813    }
814
815    /// Finish writing and return the complete JSON string.
816    pub fn finish(self) -> String {
817        self.buffer
818    }
819
820    fn maybe_comma(&mut self) {
821        if let Some(&(_, needs_comma)) = self.stack.last()
822            && needs_comma
823        {
824            self.buffer.push(',');
825        }
826    }
827}
828
829impl Default for JsonStreamingWriter {
830    fn default() -> Self {
831        Self::new()
832    }
833}
834
835// ─── JSON Schema Generation ───────────────────────────────────────────────────
836
837impl JsonSchema {
838    /// Infer a `JsonSchema` from a sample value.
839    ///
840    /// Arrays use the schema of their first element; objects use the schemas
841    /// of their values.
842    pub fn infer_from(value: &JsonValue) -> Self {
843        match value {
844            JsonValue::Null => JsonSchema::Null,
845            JsonValue::Bool(_) => JsonSchema::Bool,
846            JsonValue::Number(_) => JsonSchema::Number,
847            JsonValue::Str(_) => JsonSchema::StringType,
848            JsonValue::Array(arr) => {
849                let inner = arr.first().map(Self::infer_from).unwrap_or(JsonSchema::Any);
850                JsonSchema::ArrayOf(Box::new(inner))
851            }
852            JsonValue::Object(pairs) => {
853                let required = pairs
854                    .iter()
855                    .map(|(k, v)| (k.clone(), Self::infer_from(v)))
856                    .collect();
857                JsonSchema::ObjectWith {
858                    required,
859                    allow_extra: true,
860                }
861            }
862        }
863    }
864
865    /// Serialize the schema to a human-readable string.
866    pub fn to_schema_string(&self) -> String {
867        match self {
868            JsonSchema::Any => "any".to_string(),
869            JsonSchema::Null => "null".to_string(),
870            JsonSchema::Bool => "boolean".to_string(),
871            JsonSchema::Number => "number".to_string(),
872            JsonSchema::StringType => "string".to_string(),
873            JsonSchema::ArrayOf(inner) => format!("array<{}>", inner.to_schema_string()),
874            JsonSchema::ObjectWith {
875                required,
876                allow_extra,
877            } => {
878                let fields: Vec<String> = required
879                    .iter()
880                    .map(|(k, s)| format!("{}: {}", k, s.to_schema_string()))
881                    .collect();
882                let extra = if *allow_extra { ", ..." } else { "" };
883                format!("object{{{}{}}}", fields.join(", "), extra)
884            }
885        }
886    }
887
888    /// Generate a JSON Schema draft-07 compatible object.
889    pub fn to_json_schema_object(&self) -> JsonValue {
890        match self {
891            JsonSchema::Any => JsonValue::Object(vec![]),
892            JsonSchema::Null => {
893                JsonValue::Object(vec![("type".into(), JsonValue::Str("null".into()))])
894            }
895            JsonSchema::Bool => {
896                JsonValue::Object(vec![("type".into(), JsonValue::Str("boolean".into()))])
897            }
898            JsonSchema::Number => {
899                JsonValue::Object(vec![("type".into(), JsonValue::Str("number".into()))])
900            }
901            JsonSchema::StringType => {
902                JsonValue::Object(vec![("type".into(), JsonValue::Str("string".into()))])
903            }
904            JsonSchema::ArrayOf(inner) => JsonValue::Object(vec![
905                ("type".into(), JsonValue::Str("array".into())),
906                ("items".into(), inner.to_json_schema_object()),
907            ]),
908            JsonSchema::ObjectWith {
909                required,
910                allow_extra,
911            } => {
912                let props: Vec<(String, JsonValue)> = required
913                    .iter()
914                    .map(|(k, s)| (k.clone(), s.to_json_schema_object()))
915                    .collect();
916                let req_names: Vec<JsonValue> = required
917                    .iter()
918                    .map(|(k, _)| JsonValue::Str(k.clone()))
919                    .collect();
920                JsonValue::Object(vec![
921                    ("type".into(), JsonValue::Str("object".into())),
922                    ("properties".into(), JsonValue::Object(props)),
923                    ("required".into(), JsonValue::Array(req_names)),
924                    ("additionalProperties".into(), JsonValue::Bool(*allow_extra)),
925                ])
926            }
927        }
928    }
929}
930
931// ─── JSON Patch / Diff Operations ────────────────────────────────────────────
932
933/// A single JSON patch operation (RFC 6902 inspired).
934#[derive(Debug, Clone)]
935pub enum JsonPatch {
936    /// Add a key-value pair to an object (or replace if key exists).
937    Add {
938        /// Dot-separated path to the field.
939        path: String,
940        /// Value to insert.
941        value: JsonValue,
942    },
943    /// Remove a key from an object.
944    Remove {
945        /// Dot-separated path to the field to remove.
946        path: String,
947    },
948    /// Replace the value at the given path.
949    Replace {
950        /// Dot-separated path to the field.
951        path: String,
952        /// New value.
953        value: JsonValue,
954    },
955    /// Copy a value from one path to another.
956    Copy {
957        /// Source path.
958        from: String,
959        /// Destination path.
960        to: String,
961    },
962}
963
964impl JsonPatch {
965    /// Apply this patch operation to a JSON value in-place.
966    pub fn apply(&self, doc: &mut JsonValue) -> Result<(), String> {
967        match self {
968            JsonPatch::Add { path, value } => patch_set_key(doc, path, value.clone()),
969            JsonPatch::Remove { path } => patch_remove_key(doc, path),
970            JsonPatch::Replace { path, value } => patch_set_key(doc, path, value.clone()),
971            JsonPatch::Copy { from, to } => {
972                let val = patch_get_key(doc, from)
973                    .ok_or_else(|| format!("path '{}' not found for copy", from))?
974                    .clone();
975                patch_set_key(doc, to, val)
976            }
977        }
978    }
979
980    /// Serialize this patch to a JSON object.
981    pub fn to_json(&self) -> JsonValue {
982        match self {
983            JsonPatch::Add { path, value } => JsonValue::Object(vec![
984                ("op".into(), JsonValue::Str("add".into())),
985                ("path".into(), JsonValue::Str(path.clone())),
986                ("value".into(), value.clone()),
987            ]),
988            JsonPatch::Remove { path } => JsonValue::Object(vec![
989                ("op".into(), JsonValue::Str("remove".into())),
990                ("path".into(), JsonValue::Str(path.clone())),
991            ]),
992            JsonPatch::Replace { path, value } => JsonValue::Object(vec![
993                ("op".into(), JsonValue::Str("replace".into())),
994                ("path".into(), JsonValue::Str(path.clone())),
995                ("value".into(), value.clone()),
996            ]),
997            JsonPatch::Copy { from, to } => JsonValue::Object(vec![
998                ("op".into(), JsonValue::Str("copy".into())),
999                ("from".into(), JsonValue::Str(from.clone())),
1000                ("path".into(), JsonValue::Str(to.clone())),
1001            ]),
1002        }
1003    }
1004}
1005
1006fn patch_set_key(doc: &mut JsonValue, key: &str, value: JsonValue) -> Result<(), String> {
1007    if let JsonValue::Object(pairs) = doc {
1008        if let Some(pos) = pairs.iter().position(|(k, _)| k == key) {
1009            pairs[pos].1 = value;
1010        } else {
1011            pairs.push((key.to_string(), value));
1012        }
1013        Ok(())
1014    } else {
1015        Err(format!("cannot set key '{}' on non-object", key))
1016    }
1017}
1018
1019fn patch_remove_key(doc: &mut JsonValue, key: &str) -> Result<(), String> {
1020    if let JsonValue::Object(pairs) = doc {
1021        let before = pairs.len();
1022        pairs.retain(|(k, _)| k != key);
1023        if pairs.len() < before {
1024            Ok(())
1025        } else {
1026            Err(format!("key '{}' not found for remove", key))
1027        }
1028    } else {
1029        Err(format!("cannot remove key '{}' from non-object", key))
1030    }
1031}
1032
1033fn patch_get_key<'a>(doc: &'a JsonValue, key: &str) -> Option<&'a JsonValue> {
1034    doc.get(key)
1035}
1036
1037/// Apply a sequence of patch operations to a document.
1038pub fn apply_patch_sequence(doc: &mut JsonValue, patches: &[JsonPatch]) -> Result<(), String> {
1039    for patch in patches {
1040        patch.apply(doc)?;
1041    }
1042    Ok(())
1043}
1044
1045/// Generate a sequence of `JsonPatch` operations that transforms `left` into `right`.
1046pub fn generate_patch(left: &JsonValue, right: &JsonValue) -> Vec<JsonPatch> {
1047    let diffs = json_diff(left, right);
1048    diffs
1049        .into_iter()
1050        .map(|diff| match (diff.left, diff.right) {
1051            (None, Some(v)) => JsonPatch::Add {
1052                path: diff.path,
1053                value: v,
1054            },
1055            (Some(_), None) => JsonPatch::Remove { path: diff.path },
1056            (Some(_), Some(v)) => JsonPatch::Replace {
1057                path: diff.path,
1058                value: v,
1059            },
1060            (None, None) => JsonPatch::Remove { path: diff.path },
1061        })
1062        .collect()
1063}
1064
1065// ─── JSONPath Query ───────────────────────────────────────────────────────────
1066
1067/// Evaluate a basic JSONPath expression against a JSON value.
1068///
1069/// Supported syntax:
1070/// - `$` — root document
1071/// - `$.a.b` — nested field access
1072/// - `$[n]` — array index
1073/// - `$.*` or `$[*]` — all children
1074/// - Combinations: `$.a[0].b`
1075pub fn jsonpath_query<'a>(root: &'a JsonValue, path: &str) -> Vec<&'a JsonValue> {
1076    let path = path.trim();
1077    if path == "$" {
1078        return vec![root];
1079    }
1080    if !path.starts_with('$') {
1081        return vec![];
1082    }
1083
1084    // Parse path segments
1085    let rest = &path[1..]; // drop '$'
1086    let segments = parse_jsonpath_segments(rest);
1087    let mut current: Vec<&'a JsonValue> = vec![root];
1088
1089    for seg in segments {
1090        let mut next = Vec::new();
1091        for node in current {
1092            match seg.as_str() {
1093                "*" => match node {
1094                    JsonValue::Object(pairs) => {
1095                        for (_, v) in pairs {
1096                            next.push(v);
1097                        }
1098                    }
1099                    JsonValue::Array(arr) => {
1100                        for v in arr {
1101                            next.push(v);
1102                        }
1103                    }
1104                    _ => {}
1105                },
1106                s if s.starts_with('[') && s.ends_with(']') => {
1107                    let inner = &s[1..s.len() - 1];
1108                    if inner == "*" {
1109                        if let JsonValue::Array(arr) = node {
1110                            for v in arr {
1111                                next.push(v);
1112                            }
1113                        }
1114                    } else if let Ok(idx) = inner.parse::<usize>()
1115                        && let JsonValue::Array(arr) = node
1116                        && let Some(v) = arr.get(idx)
1117                    {
1118                        next.push(v);
1119                    }
1120                }
1121                key => {
1122                    if let Some(v) = node.get(key) {
1123                        next.push(v);
1124                    }
1125                }
1126            }
1127        }
1128        current = next;
1129    }
1130    current
1131}
1132
1133fn parse_jsonpath_segments(s: &str) -> Vec<String> {
1134    let mut segments = Vec::new();
1135    let mut remaining = s;
1136
1137    while !remaining.is_empty() {
1138        if remaining.starts_with('.') {
1139            remaining = &remaining[1..];
1140            // Read until next '.' or '['
1141            let end = remaining.find(['.', '[']).unwrap_or(remaining.len());
1142            if end > 0 {
1143                segments.push(remaining[..end].to_string());
1144                remaining = &remaining[end..];
1145            }
1146        } else if remaining.starts_with('[') {
1147            let end = remaining.find(']').unwrap_or(remaining.len() - 1);
1148            segments.push(remaining[..=end].to_string());
1149            remaining = &remaining[(end + 1)..];
1150        } else {
1151            // Try reading as bare word
1152            let end = remaining.find(['.', '[']).unwrap_or(remaining.len());
1153            segments.push(remaining[..end].to_string());
1154            remaining = &remaining[end..];
1155        }
1156    }
1157    segments
1158}
1159
1160// ─── JSON Compression ─────────────────────────────────────────────────────────
1161
1162/// Compress a JSON string by removing insignificant whitespace.
1163///
1164/// Preserves whitespace inside string values.
1165pub fn json_compress(input: &str) -> String {
1166    let mut out = String::with_capacity(input.len());
1167    let mut in_string = false;
1168    let mut chars = input.chars().peekable();
1169
1170    while let Some(c) = chars.next() {
1171        if in_string {
1172            out.push(c);
1173            if c == '\\' {
1174                // Escaped character: consume the next char verbatim
1175                if let Some(next) = chars.next() {
1176                    out.push(next);
1177                }
1178            } else if c == '"' {
1179                in_string = false;
1180            }
1181        } else {
1182            match c {
1183                '"' => {
1184                    in_string = true;
1185                    out.push(c);
1186                }
1187                ' ' | '\t' | '\n' | '\r' => {
1188                    // Skip whitespace outside strings
1189                }
1190                _ => {
1191                    out.push(c);
1192                }
1193            }
1194        }
1195    }
1196    out
1197}
1198
1199/// Statistics about JSON compression.
1200#[derive(Debug, Clone)]
1201pub struct JsonCompressionStats {
1202    /// Original string length.
1203    pub original_len: usize,
1204    /// Compressed string length.
1205    pub compressed_len: usize,
1206}
1207
1208impl JsonCompressionStats {
1209    /// Compute compression statistics for a JSON string.
1210    pub fn compute(input: &str) -> Self {
1211        let compressed = json_compress(input);
1212        Self {
1213            original_len: input.len(),
1214            compressed_len: compressed.len(),
1215        }
1216    }
1217
1218    /// Compression ratio (compressed / original). Lower is better.
1219    pub fn compression_ratio(&self) -> f64 {
1220        if self.original_len == 0 {
1221            return 1.0;
1222        }
1223        self.compressed_len as f64 / self.original_len as f64
1224    }
1225
1226    /// Bytes saved by compression.
1227    pub fn bytes_saved(&self) -> usize {
1228        self.original_len.saturating_sub(self.compressed_len)
1229    }
1230}
1231
1232// ─── Tests ────────────────────────────────────────────────────────────────────
1233
1234#[cfg(test)]
1235mod tests {
1236    use super::*;
1237
1238    #[test]
1239    fn test_null_roundtrip() {
1240        let j = JsonValue::Null;
1241        let s = j.to_json_string();
1242        assert_eq!(s, "null");
1243        let parsed = JsonValue::from_str(&s).unwrap();
1244        assert_eq!(parsed, JsonValue::Null);
1245    }
1246
1247    #[test]
1248    fn test_bool_true() {
1249        let j = JsonValue::Bool(true);
1250        let s = j.to_json_string();
1251        assert_eq!(s, "true");
1252        assert_eq!(JsonValue::from_str(&s).unwrap(), JsonValue::Bool(true));
1253    }
1254
1255    #[test]
1256    fn test_bool_false() {
1257        let j = JsonValue::Bool(false);
1258        assert_eq!(j.to_json_string(), "false");
1259        assert_eq!(
1260            JsonValue::from_str("false").unwrap(),
1261            JsonValue::Bool(false)
1262        );
1263    }
1264
1265    #[test]
1266    fn test_number_integer() {
1267        let j = JsonValue::Number(42.0);
1268        let s = j.to_json_string();
1269        assert_eq!(s, "42");
1270        let parsed = JsonValue::from_str(&s).unwrap();
1271        assert_eq!(parsed.as_f64(), Some(42.0));
1272    }
1273
1274    #[test]
1275    fn test_number_float() {
1276        let j = JsonValue::Number(3.125);
1277        let s = j.to_json_string();
1278        let parsed = JsonValue::from_str(&s).unwrap();
1279        let v = parsed.as_f64().unwrap();
1280        assert!((v - 3.125).abs() < 1e-10);
1281    }
1282
1283    #[test]
1284    fn test_string_roundtrip() {
1285        let j = JsonValue::Str("hello world".to_string());
1286        let s = j.to_json_string();
1287        assert_eq!(s, "\"hello world\"");
1288        let parsed = JsonValue::from_str(&s).unwrap();
1289        assert_eq!(parsed.as_str(), Some("hello world"));
1290    }
1291
1292    #[test]
1293    fn test_string_escape() {
1294        let j = JsonValue::Str("line1\nline2".to_string());
1295        let s = j.to_json_string();
1296        assert!(s.contains("\\n"));
1297        let parsed = JsonValue::from_str(&s).unwrap();
1298        assert_eq!(parsed.as_str(), Some("line1\nline2"));
1299    }
1300
1301    #[test]
1302    fn test_array_roundtrip() {
1303        let j = JsonValue::Array(vec![
1304            JsonValue::Number(1.0),
1305            JsonValue::Number(2.0),
1306            JsonValue::Number(3.0),
1307        ]);
1308        let s = j.to_json_string();
1309        let parsed = JsonValue::from_str(&s).unwrap();
1310        let arr = parsed.as_array().unwrap();
1311        assert_eq!(arr.len(), 3);
1312        assert_eq!(arr[0].as_f64(), Some(1.0));
1313    }
1314
1315    #[test]
1316    fn test_empty_array() {
1317        let j = JsonValue::Array(vec![]);
1318        assert_eq!(j.to_json_string(), "[]");
1319        let parsed = JsonValue::from_str("[]").unwrap();
1320        assert_eq!(parsed.as_array().unwrap().len(), 0);
1321    }
1322
1323    #[test]
1324    fn test_object_get() {
1325        let j = JsonValue::Object(vec![
1326            ("x".to_string(), JsonValue::Number(1.0)),
1327            ("y".to_string(), JsonValue::Number(2.0)),
1328        ]);
1329        assert_eq!(j.get("x").unwrap().as_f64(), Some(1.0));
1330        assert!(j.get("z").is_none());
1331    }
1332
1333    #[test]
1334    fn test_object_roundtrip() {
1335        let j = JsonValue::Object(vec![
1336            ("name".to_string(), JsonValue::Str("particle".to_string())),
1337            ("mass".to_string(), JsonValue::Number(1.5)),
1338        ]);
1339        let s = j.to_json_string();
1340        let parsed = JsonValue::from_str(&s).unwrap();
1341        assert_eq!(parsed.get("name").unwrap().as_str(), Some("particle"));
1342        assert_eq!(parsed.get("mass").unwrap().as_f64(), Some(1.5));
1343    }
1344
1345    #[test]
1346    fn test_nested_object() {
1347        let s = r#"{"pos":{"x":1,"y":2}}"#;
1348        let parsed = JsonValue::from_str(s).unwrap();
1349        let pos = parsed.get("pos").unwrap();
1350        assert_eq!(pos.get("x").unwrap().as_f64(), Some(1.0));
1351    }
1352
1353    #[test]
1354    fn test_get_index() {
1355        let j = JsonValue::Array(vec![JsonValue::Number(10.0), JsonValue::Number(20.0)]);
1356        assert_eq!(j.get_index(0).unwrap().as_f64(), Some(10.0));
1357        assert!(j.get_index(5).is_none());
1358    }
1359
1360    #[test]
1361    fn test_vec3_roundtrip() {
1362        let v = [1.0, 2.0, 3.0f64];
1363        let j = vec3_to_json(v);
1364        let v2 = json_to_vec3(&j).unwrap();
1365        assert_eq!(v, v2);
1366    }
1367
1368    #[test]
1369    fn test_json_to_vec3_too_short() {
1370        let j = JsonValue::Array(vec![JsonValue::Number(1.0)]);
1371        assert!(json_to_vec3(&j).is_none());
1372    }
1373
1374    #[test]
1375    fn test_particles_to_json() {
1376        let pos = [[0.0, 0.0, 0.0], [1.0, 2.0, 3.0]];
1377        let vel = [[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]];
1378        let j = particles_to_json(&pos, &vel);
1379        let arr = j.get("particles").unwrap().as_array().unwrap();
1380        assert_eq!(arr.len(), 2);
1381        let p0 = &arr[0];
1382        let p0_pos = json_to_vec3(p0.get("position").unwrap()).unwrap();
1383        assert_eq!(p0_pos, [0.0, 0.0, 0.0]);
1384    }
1385
1386    #[test]
1387    fn test_parse_negative_number() {
1388        let j = JsonValue::from_str("-3.125").unwrap();
1389        assert!((j.as_f64().unwrap() + 3.125).abs() < 1e-10);
1390    }
1391
1392    #[test]
1393    fn test_whitespace_tolerance() {
1394        let s = "  {  \"k\"  :  42  }  ";
1395        let j = JsonValue::from_str(s).unwrap();
1396        assert_eq!(j.get("k").unwrap().as_f64(), Some(42.0));
1397    }
1398
1399    #[test]
1400    fn test_empty_object() {
1401        let j = JsonValue::from_str("{}").unwrap();
1402        assert!(j.get("anything").is_none());
1403    }
1404
1405    #[test]
1406    fn test_invalid_input_error() {
1407        assert!(JsonValue::from_str("not_json").is_err());
1408    }
1409
1410    // ── New tests for expanded features ─────────────────────────────────────
1411
1412    #[test]
1413    fn test_get_path() {
1414        let s = r#"{"a":{"b":{"c":42}}}"#;
1415        let j = JsonValue::from_str(s).unwrap();
1416        assert_eq!(j.get_path("a.b.c").unwrap().as_f64(), Some(42.0));
1417        assert!(j.get_path("a.b.d").is_none());
1418        assert!(j.get_path("x").is_none());
1419    }
1420
1421    #[test]
1422    fn test_json_len_and_is_empty() {
1423        let arr = JsonValue::Array(vec![JsonValue::Number(1.0), JsonValue::Number(2.0)]);
1424        assert_eq!(arr.len(), 2);
1425        assert!(!arr.is_empty());
1426
1427        let empty_arr = JsonValue::Array(vec![]);
1428        assert_eq!(empty_arr.len(), 0);
1429        assert!(empty_arr.is_empty());
1430
1431        assert!(JsonValue::Null.is_empty());
1432        assert!(!JsonValue::Number(1.0).is_empty());
1433    }
1434
1435    #[test]
1436    fn test_json_merge() {
1437        let base = JsonValue::from_str(r#"{"a":1,"b":2}"#).unwrap();
1438        let overlay = JsonValue::from_str(r#"{"b":3,"c":4}"#).unwrap();
1439        let merged = base.merge(&overlay);
1440        assert_eq!(merged.get("a").unwrap().as_f64(), Some(1.0));
1441        assert_eq!(merged.get("b").unwrap().as_f64(), Some(3.0));
1442        assert_eq!(merged.get("c").unwrap().as_f64(), Some(4.0));
1443    }
1444
1445    #[test]
1446    fn test_json_merge_nested() {
1447        let base = JsonValue::from_str(r#"{"a":{"x":1,"y":2}}"#).unwrap();
1448        let overlay = JsonValue::from_str(r#"{"a":{"y":3,"z":4}}"#).unwrap();
1449        let merged = base.merge(&overlay);
1450        let a = merged.get("a").unwrap();
1451        assert_eq!(a.get("x").unwrap().as_f64(), Some(1.0));
1452        assert_eq!(a.get("y").unwrap().as_f64(), Some(3.0));
1453        assert_eq!(a.get("z").unwrap().as_f64(), Some(4.0));
1454    }
1455
1456    #[test]
1457    fn test_json_keys_values() {
1458        let j = JsonValue::from_str(r#"{"a":1,"b":2}"#).unwrap();
1459        let keys = j.keys();
1460        assert_eq!(keys.len(), 2);
1461        assert!(keys.contains(&"a"));
1462        assert!(keys.contains(&"b"));
1463        let vals = j.values();
1464        assert_eq!(vals.len(), 2);
1465    }
1466
1467    #[test]
1468    fn test_schema_validation_number() {
1469        let schema = JsonSchema::Number;
1470        assert!(schema.validate(&JsonValue::Number(42.0)).is_ok());
1471        assert!(schema.validate(&JsonValue::Str("nope".into())).is_err());
1472    }
1473
1474    #[test]
1475    fn test_schema_validation_object() {
1476        let schema = JsonSchema::ObjectWith {
1477            required: vec![
1478                ("name".into(), JsonSchema::StringType),
1479                ("mass".into(), JsonSchema::Number),
1480            ],
1481            allow_extra: true,
1482        };
1483        let valid = JsonValue::from_str(r#"{"name":"ball","mass":1.5,"extra":true}"#).unwrap();
1484        assert!(schema.validate(&valid).is_ok());
1485
1486        let missing_mass = JsonValue::from_str(r#"{"name":"ball"}"#).unwrap();
1487        assert!(schema.validate(&missing_mass).is_err());
1488    }
1489
1490    #[test]
1491    fn test_schema_no_extra_keys() {
1492        let schema = JsonSchema::ObjectWith {
1493            required: vec![("x".into(), JsonSchema::Number)],
1494            allow_extra: false,
1495        };
1496        let valid = JsonValue::from_str(r#"{"x":1}"#).unwrap();
1497        assert!(schema.validate(&valid).is_ok());
1498
1499        let extra = JsonValue::from_str(r#"{"x":1,"y":2}"#).unwrap();
1500        assert!(schema.validate(&extra).is_err());
1501    }
1502
1503    #[test]
1504    fn test_schema_array_of() {
1505        let schema = JsonSchema::ArrayOf(Box::new(JsonSchema::Number));
1506        let valid = JsonValue::from_str("[1,2,3]").unwrap();
1507        assert!(schema.validate(&valid).is_ok());
1508
1509        let invalid = JsonValue::from_str(r#"[1,"two",3]"#).unwrap();
1510        assert!(schema.validate(&invalid).is_err());
1511    }
1512
1513    #[test]
1514    fn test_json_diff_same() {
1515        let a = JsonValue::from_str(r#"{"x":1}"#).unwrap();
1516        let b = JsonValue::from_str(r#"{"x":1}"#).unwrap();
1517        let diffs = json_diff(&a, &b);
1518        assert!(diffs.is_empty());
1519    }
1520
1521    #[test]
1522    fn test_json_diff_different_value() {
1523        let a = JsonValue::from_str(r#"{"x":1}"#).unwrap();
1524        let b = JsonValue::from_str(r#"{"x":2}"#).unwrap();
1525        let diffs = json_diff(&a, &b);
1526        assert_eq!(diffs.len(), 1);
1527        assert_eq!(diffs[0].path, "x");
1528    }
1529
1530    #[test]
1531    fn test_json_diff_added_key() {
1532        let a = JsonValue::from_str(r#"{"x":1}"#).unwrap();
1533        let b = JsonValue::from_str(r#"{"x":1,"y":2}"#).unwrap();
1534        let diffs = json_diff(&a, &b);
1535        assert_eq!(diffs.len(), 1);
1536        assert_eq!(diffs[0].path, "y");
1537        assert!(diffs[0].left.is_none());
1538        assert!(diffs[0].right.is_some());
1539    }
1540
1541    #[test]
1542    fn test_json_diff_removed_key() {
1543        let a = JsonValue::from_str(r#"{"x":1,"y":2}"#).unwrap();
1544        let b = JsonValue::from_str(r#"{"x":1}"#).unwrap();
1545        let diffs = json_diff(&a, &b);
1546        assert_eq!(diffs.len(), 1);
1547        assert_eq!(diffs[0].path, "y");
1548        assert!(diffs[0].left.is_some());
1549        assert!(diffs[0].right.is_none());
1550    }
1551
1552    #[test]
1553    fn test_json_diff_array() {
1554        let a = JsonValue::from_str("[1,2,3]").unwrap();
1555        let b = JsonValue::from_str("[1,2,4]").unwrap();
1556        let diffs = json_diff(&a, &b);
1557        assert_eq!(diffs.len(), 1);
1558        assert!(diffs[0].path.contains("[2]"));
1559    }
1560
1561    #[test]
1562    fn test_streaming_parser() {
1563        let mut parser = JsonStreamParser::new();
1564        let count = parser.feed(r#"[{"a":1},{"b":2}]"#);
1565        assert_eq!(count, 2);
1566        let results = parser.take_results();
1567        assert_eq!(results.len(), 2);
1568        assert_eq!(results[0].get("a").unwrap().as_f64(), Some(1.0));
1569        assert_eq!(results[1].get("b").unwrap().as_f64(), Some(2.0));
1570    }
1571
1572    #[test]
1573    fn test_streaming_parser_incremental() {
1574        let mut parser = JsonStreamParser::new();
1575        parser.feed("[42,");
1576        assert_eq!(parser.result_count(), 1);
1577        parser.feed("43]");
1578        assert_eq!(parser.result_count(), 2);
1579        let results = parser.take_results();
1580        assert_eq!(results[0].as_f64(), Some(42.0));
1581        assert_eq!(results[1].as_f64(), Some(43.0));
1582    }
1583
1584    #[test]
1585    fn test_records_to_json() {
1586        let records = vec![
1587            vec![
1588                ("id".into(), JsonValue::Number(1.0)),
1589                ("name".into(), JsonValue::Str("a".into())),
1590            ],
1591            vec![
1592                ("id".into(), JsonValue::Number(2.0)),
1593                ("name".into(), JsonValue::Str("b".into())),
1594            ],
1595        ];
1596        let j = records_to_json(&records);
1597        let arr = j.as_array().unwrap();
1598        assert_eq!(arr.len(), 2);
1599        assert_eq!(arr[0].get("id").unwrap().as_f64(), Some(1.0));
1600    }
1601
1602    #[test]
1603    fn test_json_to_records() {
1604        let s = r#"[{"id":1},{"id":2}]"#;
1605        let j = JsonValue::from_str(s).unwrap();
1606        let records = json_to_records(&j).unwrap();
1607        assert_eq!(records.len(), 2);
1608    }
1609
1610    #[test]
1611    fn test_pretty_print() {
1612        let j = JsonValue::from_str(r#"{"a":1,"b":[2,3]}"#).unwrap();
1613        let pretty = j.to_json_pretty(2);
1614        assert!(pretty.contains('\n'));
1615        assert!(pretty.contains("\"a\""));
1616        // Should be parseable back
1617        let reparsed = JsonValue::from_str(&pretty).unwrap();
1618        assert_eq!(reparsed.get("a").unwrap().as_f64(), Some(1.0));
1619    }
1620
1621    #[test]
1622    fn test_as_bool() {
1623        assert_eq!(JsonValue::Bool(true).as_bool(), Some(true));
1624        assert_eq!(JsonValue::Number(1.0).as_bool(), None);
1625    }
1626
1627    #[test]
1628    fn test_as_object() {
1629        let j = JsonValue::from_str(r#"{"a":1}"#).unwrap();
1630        assert!(j.as_object().is_some());
1631        assert!(JsonValue::Number(1.0).as_object().is_none());
1632    }
1633
1634    #[test]
1635    fn test_matrix_to_json() {
1636        let m = vec![vec![1.0, 2.0], vec![3.0, 4.0]];
1637        let j = matrix_to_json(&m);
1638        let arr = j.as_array().unwrap();
1639        assert_eq!(arr.len(), 2);
1640        assert_eq!(arr[0].get_index(0).unwrap().as_f64(), Some(1.0));
1641        assert_eq!(arr[1].get_index(1).unwrap().as_f64(), Some(4.0));
1642    }
1643
1644    // ── JSON streaming writer tests ───────────────────────────────────────
1645
1646    #[test]
1647    fn test_streaming_writer_empty_array() {
1648        let mut w = JsonStreamingWriter::new();
1649        w.begin_array();
1650        w.end_array();
1651        let s = w.finish();
1652        assert_eq!(s, "[]");
1653    }
1654
1655    #[test]
1656    fn test_streaming_writer_single_value() {
1657        let mut w = JsonStreamingWriter::new();
1658        w.begin_array();
1659        w.write_value(&JsonValue::Number(42.0));
1660        w.end_array();
1661        let s = w.finish();
1662        assert_eq!(
1663            JsonValue::from_str(&s).unwrap(),
1664            JsonValue::Array(vec![JsonValue::Number(42.0)])
1665        );
1666    }
1667
1668    #[test]
1669    fn test_streaming_writer_multiple_values() {
1670        let mut w = JsonStreamingWriter::new();
1671        w.begin_array();
1672        w.write_value(&JsonValue::Number(1.0));
1673        w.write_value(&JsonValue::Number(2.0));
1674        w.write_value(&JsonValue::Str("three".to_string()));
1675        w.end_array();
1676        let s = w.finish();
1677        let parsed = JsonValue::from_str(&s).unwrap();
1678        let arr = parsed.as_array().unwrap();
1679        assert_eq!(arr.len(), 3);
1680    }
1681
1682    #[test]
1683    fn test_streaming_writer_nested_object() {
1684        let mut w = JsonStreamingWriter::new();
1685        w.begin_object();
1686        w.write_key("x");
1687        w.write_value(&JsonValue::Number(1.0));
1688        w.write_key("y");
1689        w.write_value(&JsonValue::Number(2.0));
1690        w.end_object();
1691        let s = w.finish();
1692        let parsed = JsonValue::from_str(&s).unwrap();
1693        assert_eq!(parsed.get("x").unwrap().as_f64(), Some(1.0));
1694    }
1695
1696    // ── JSON schema generation tests ──────────────────────────────────────
1697
1698    #[test]
1699    fn test_schema_from_number() {
1700        let v = JsonValue::Number(42.0);
1701        let schema = JsonSchema::infer_from(&v);
1702        assert!(schema.validate(&JsonValue::Number(1.0)).is_ok());
1703        assert!(schema.validate(&JsonValue::Str("nope".into())).is_err());
1704    }
1705
1706    #[test]
1707    fn test_schema_from_object() {
1708        let j = JsonValue::from_str(r#"{"name":"alice","age":30}"#).unwrap();
1709        let schema = JsonSchema::infer_from(&j);
1710        let valid = JsonValue::from_str(r#"{"name":"bob","age":25}"#).unwrap();
1711        assert!(schema.validate(&valid).is_ok());
1712    }
1713
1714    #[test]
1715    fn test_schema_from_array() {
1716        let j = JsonValue::from_str("[1,2,3]").unwrap();
1717        let schema = JsonSchema::infer_from(&j);
1718        let valid = JsonValue::from_str("[4,5,6]").unwrap();
1719        assert!(schema.validate(&valid).is_ok());
1720        let invalid = JsonValue::from_str(r#"["a","b"]"#).unwrap();
1721        assert!(schema.validate(&invalid).is_err());
1722    }
1723
1724    #[test]
1725    fn test_schema_to_string() {
1726        let schema = JsonSchema::Number;
1727        let s = schema.to_schema_string();
1728        assert!(s.contains("number"));
1729    }
1730
1731    // ── JSON patch / diff operations tests ────────────────────────────────
1732
1733    #[test]
1734    fn test_json_patch_add() {
1735        let mut doc = JsonValue::from_str(r#"{"a":1}"#).unwrap();
1736        let patch = JsonPatch::Add {
1737            path: "b".to_string(),
1738            value: JsonValue::Number(2.0),
1739        };
1740        patch.apply(&mut doc).unwrap();
1741        assert_eq!(doc.get("b").unwrap().as_f64(), Some(2.0));
1742    }
1743
1744    #[test]
1745    fn test_json_patch_remove() {
1746        let mut doc = JsonValue::from_str(r#"{"a":1,"b":2}"#).unwrap();
1747        let patch = JsonPatch::Remove {
1748            path: "b".to_string(),
1749        };
1750        patch.apply(&mut doc).unwrap();
1751        assert!(doc.get("b").is_none());
1752        assert_eq!(doc.get("a").unwrap().as_f64(), Some(1.0));
1753    }
1754
1755    #[test]
1756    fn test_json_patch_replace() {
1757        let mut doc = JsonValue::from_str(r#"{"a":1}"#).unwrap();
1758        let patch = JsonPatch::Replace {
1759            path: "a".to_string(),
1760            value: JsonValue::Number(99.0),
1761        };
1762        patch.apply(&mut doc).unwrap();
1763        assert_eq!(doc.get("a").unwrap().as_f64(), Some(99.0));
1764    }
1765
1766    #[test]
1767    fn test_json_patch_sequence() {
1768        let mut doc = JsonValue::from_str(r#"{"x":1}"#).unwrap();
1769        let patches = vec![
1770            JsonPatch::Add {
1771                path: "y".to_string(),
1772                value: JsonValue::Number(2.0),
1773            },
1774            JsonPatch::Replace {
1775                path: "x".to_string(),
1776                value: JsonValue::Str("hello".into()),
1777            },
1778        ];
1779        apply_patch_sequence(&mut doc, &patches).unwrap();
1780        assert_eq!(doc.get("y").unwrap().as_f64(), Some(2.0));
1781        assert_eq!(doc.get("x").unwrap().as_str(), Some("hello"));
1782    }
1783
1784    // ── JSONPath basics tests ──────────────────────────────────────────────
1785
1786    #[test]
1787    fn test_jsonpath_root() {
1788        let j = JsonValue::from_str(r#"{"a":1}"#).unwrap();
1789        let results = jsonpath_query(&j, "$");
1790        assert_eq!(results.len(), 1);
1791        assert_eq!(results[0].get("a").unwrap().as_f64(), Some(1.0));
1792    }
1793
1794    #[test]
1795    fn test_jsonpath_child() {
1796        let j = JsonValue::from_str(r#"{"a":{"b":42}}"#).unwrap();
1797        let results = jsonpath_query(&j, "$.a.b");
1798        assert_eq!(results.len(), 1);
1799        assert_eq!(results[0].as_f64(), Some(42.0));
1800    }
1801
1802    #[test]
1803    fn test_jsonpath_array_index() {
1804        let j = JsonValue::from_str("[10,20,30]").unwrap();
1805        let results = jsonpath_query(&j, "$[1]");
1806        assert_eq!(results.len(), 1);
1807        assert_eq!(results[0].as_f64(), Some(20.0));
1808    }
1809
1810    #[test]
1811    fn test_jsonpath_wildcard() {
1812        let j = JsonValue::from_str(r#"{"a":1,"b":2,"c":3}"#).unwrap();
1813        let results = jsonpath_query(&j, "$.*");
1814        assert_eq!(results.len(), 3);
1815    }
1816
1817    #[test]
1818    fn test_jsonpath_array_wildcard() {
1819        let j = JsonValue::from_str("[1,2,3]").unwrap();
1820        let results = jsonpath_query(&j, "$[*]");
1821        assert_eq!(results.len(), 3);
1822    }
1823
1824    // ── JSON compression tests ─────────────────────────────────────────────
1825
1826    #[test]
1827    fn test_json_compress_removes_whitespace() {
1828        let pretty = r#"{
1829  "a": 1,
1830  "b": [
1831    2,
1832    3
1833  ]
1834}"#;
1835        let compressed = json_compress(pretty);
1836        assert!(
1837            !compressed.contains('\n'),
1838            "compressed should not contain newlines"
1839        );
1840        assert!(
1841            !compressed.contains("  "),
1842            "compressed should not contain double spaces"
1843        );
1844        let parsed = JsonValue::from_str(&compressed).unwrap();
1845        assert_eq!(parsed.get("a").unwrap().as_f64(), Some(1.0));
1846    }
1847
1848    #[test]
1849    fn test_json_compress_roundtrip() {
1850        let j = JsonValue::from_str(r#"{"x":1,"y":[2,3],"z":true}"#).unwrap();
1851        let pretty = j.to_json_pretty(4);
1852        let compressed = json_compress(&pretty);
1853        let reparsed = JsonValue::from_str(&compressed).unwrap();
1854        assert_eq!(reparsed, j);
1855    }
1856
1857    #[test]
1858    fn test_json_compress_stats() {
1859        let pretty = r#"{
1860  "alpha": 1,
1861  "beta":  2
1862}"#;
1863        let stats = JsonCompressionStats::compute(pretty);
1864        assert!(
1865            stats.original_len > stats.compressed_len,
1866            "compression should reduce size: {} > {}",
1867            stats.original_len,
1868            stats.compressed_len
1869        );
1870        assert!(stats.compression_ratio() < 1.0);
1871    }
1872
1873    #[test]
1874    fn test_json_compress_preserves_strings_with_spaces() {
1875        let j = JsonValue::from_str(r#"{"msg":"hello world"}"#).unwrap();
1876        let pretty = j.to_json_pretty(2);
1877        let compressed = json_compress(&pretty);
1878        let reparsed = JsonValue::from_str(&compressed).unwrap();
1879        assert_eq!(reparsed.get("msg").unwrap().as_str(), Some("hello world"));
1880    }
1881}