toon_format/utils/
mod.rs

1pub mod literal;
2pub mod string;
3pub mod validation;
4
5use indexmap::IndexMap;
6pub use literal::{
7    is_keyword,
8    is_literal_like,
9    is_numeric_like,
10    is_structural_char,
11};
12pub use string::{
13    escape_string,
14    is_valid_unquoted_key,
15    needs_quoting,
16    quote_string,
17    unescape_string,
18};
19
20use crate::types::{
21    JsonValue as Value,
22    Number,
23};
24
25/// Context for determining when quoting is needed.
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum QuotingContext {
28    ObjectValue,
29    ArrayValue,
30}
31
32/// Normalize a JSON value (converts NaN/Infinity to null, -0 to 0).
33pub fn normalize(value: Value) -> Value {
34    match value {
35        Value::Number(n) => {
36            // Handle NegInt(0) case - convert to PosInt(0)
37            if let Number::NegInt(0) = n {
38                Value::Number(Number::from(0u64))
39            } else if let Some(f) = n.as_f64() {
40                if f.is_nan() || f.is_infinite() {
41                    Value::Null
42                } else if f == 0.0 && f.is_sign_negative() {
43                    Value::Number(Number::from(0u64))
44                } else {
45                    Value::Number(n)
46                }
47            } else {
48                Value::Number(n)
49            }
50        }
51        Value::Object(obj) => {
52            let normalized: IndexMap<String, Value> =
53                obj.into_iter().map(|(k, v)| (k, normalize(v))).collect();
54            Value::Object(normalized)
55        }
56        Value::Array(arr) => {
57            let normalized: Vec<Value> = arr.into_iter().map(normalize).collect();
58            Value::Array(normalized)
59        }
60        _ => value,
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use core::f64;
67
68    use serde_json::json;
69
70    use super::*;
71
72    #[test]
73    fn test_normalize_nan() {
74        let value = Value::from(json!(f64::NAN));
75        let normalized = normalize(value);
76        assert_eq!(normalized, Value::from(json!(null)));
77    }
78
79    #[test]
80    fn test_normalize_infinity() {
81        let value = Value::from(json!(f64::INFINITY));
82        let normalized = normalize(value);
83        assert_eq!(normalized, Value::from(json!(null)));
84
85        let value = Value::from(json!(f64::NEG_INFINITY));
86        let normalized = normalize(value);
87        assert_eq!(normalized, Value::from(json!(null)));
88    }
89
90    #[test]
91    fn test_normalize_negative_zero() {
92        let value = Value::from(json!(-0.0));
93        let normalized = normalize(value);
94        assert_eq!(normalized, Value::from(json!(0)));
95    }
96
97    #[test]
98    fn test_normalize_nested() {
99        let value = Value::from(json!({
100            "a": f64::NAN,
101            "b": {
102                "c": f64::INFINITY
103            },
104            "d": [1, f64::NAN, 3]
105        }));
106
107        let normalized = normalize(value);
108        assert_eq!(
109            normalized,
110            Value::from(json!({
111                "a": null,
112                "b": {
113                    "c": null
114                },
115                "d": [1, null, 3]
116            }))
117        );
118    }
119
120    #[test]
121    fn test_normalize_normal_values() {
122        let value = Value::from(json!({
123            "name": "Alice",
124            "age": 30,
125            "score": f64::consts::PI
126        }));
127
128        let normalized = normalize(value.clone());
129        assert_eq!(normalized, value);
130    }
131}