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    Key,
29    Value,
30    Header,
31}
32
33/// Normalize a JSON value (converts NaN/Infinity to null, -0 to 0).
34pub fn normalize(value: Value) -> Value {
35    match value {
36        Value::Number(n) => {
37            // Handle NegInt(0) case - convert to PosInt(0)
38            if let Number::NegInt(0) = n {
39                Value::Number(Number::from(0u64))
40            } else if let Some(f) = n.as_f64() {
41                if f.is_nan() || f.is_infinite() {
42                    Value::Null
43                } else if f == 0.0 && f.is_sign_negative() {
44                    Value::Number(Number::from(0u64))
45                } else {
46                    Value::Number(n)
47                }
48            } else {
49                Value::Number(n)
50            }
51        }
52        Value::Object(obj) => {
53            let normalized: IndexMap<String, Value> =
54                obj.into_iter().map(|(k, v)| (k, normalize(v))).collect();
55            Value::Object(normalized)
56        }
57        Value::Array(arr) => {
58            let normalized: Vec<Value> = arr.into_iter().map(normalize).collect();
59            Value::Array(normalized)
60        }
61        _ => value,
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use core::f64;
68
69    use serde_json::json;
70
71    use super::*;
72
73    #[test]
74    fn test_normalize_nan() {
75        let value = Value::from(json!(f64::NAN));
76        let normalized = normalize(value);
77        assert_eq!(normalized, Value::from(json!(null)));
78    }
79
80    #[test]
81    fn test_normalize_infinity() {
82        let value = Value::from(json!(f64::INFINITY));
83        let normalized = normalize(value);
84        assert_eq!(normalized, Value::from(json!(null)));
85
86        let value = Value::from(json!(f64::NEG_INFINITY));
87        let normalized = normalize(value);
88        assert_eq!(normalized, Value::from(json!(null)));
89    }
90
91    #[test]
92    fn test_normalize_negative_zero() {
93        let value = Value::from(json!(-0.0));
94        let normalized = normalize(value);
95        assert_eq!(normalized, Value::from(json!(0)));
96    }
97
98    #[test]
99    fn test_normalize_nested() {
100        let value = Value::from(json!({
101            "a": f64::NAN,
102            "b": {
103                "c": f64::INFINITY
104            },
105            "d": [1, f64::NAN, 3]
106        }));
107
108        let normalized = normalize(value);
109        assert_eq!(
110            normalized,
111            Value::from(json!({
112                "a": null,
113                "b": {
114                    "c": null
115                },
116                "d": [1, null, 3]
117            }))
118        );
119    }
120
121    #[test]
122    fn test_normalize_normal_values() {
123        let value = Value::from(json!({
124            "name": "Alice",
125            "age": 30,
126            "score": f64::consts::PI
127        }));
128
129        let normalized = normalize(value.clone());
130        assert_eq!(normalized, value);
131    }
132}