1use crate::{JsonArray, JsonObject, JsonPrimitive, JsonValue, StringOrNumberOrBoolOrNull};
2
3pub fn normalize_json_value(value: JsonValue) -> JsonValue {
4 match value {
5 JsonValue::Primitive(primitive) => JsonValue::Primitive(normalize_primitive(primitive)),
6 JsonValue::Array(items) => {
7 JsonValue::Array(items.into_iter().map(normalize_json_value).collect())
8 }
9 JsonValue::Object(entries) => JsonValue::Object(
10 entries
11 .into_iter()
12 .map(|(key, value)| (key, normalize_json_value(value)))
13 .collect(),
14 ),
15 }
16}
17
18#[must_use]
19pub fn normalize_primitive(value: JsonPrimitive) -> JsonPrimitive {
20 match value {
21 StringOrNumberOrBoolOrNull::Number(value) => {
22 if !value.is_finite() {
23 StringOrNumberOrBoolOrNull::Null
24 } else if value == 0.0 {
25 StringOrNumberOrBoolOrNull::Number(0.0)
26 } else {
27 StringOrNumberOrBoolOrNull::Number(value)
28 }
29 }
30 _ => value,
31 }
32}
33
34#[must_use]
35pub const fn is_json_primitive(value: &JsonValue) -> bool {
36 matches!(value, JsonValue::Primitive(_))
37}
38
39#[must_use]
40pub const fn is_json_array(value: &JsonValue) -> bool {
41 matches!(value, JsonValue::Array(_))
42}
43
44#[must_use]
45pub const fn is_json_object(value: &JsonValue) -> bool {
46 matches!(value, JsonValue::Object(_))
47}
48
49#[must_use]
50pub const fn is_empty_object(value: &JsonObject) -> bool {
51 value.is_empty()
52}
53
54#[must_use]
55pub fn is_array_of_primitives(value: &JsonArray) -> bool {
56 value
57 .iter()
58 .all(|item| matches!(item, JsonValue::Primitive(_)))
59}
60
61#[must_use]
62pub fn is_array_of_arrays(value: &JsonArray) -> bool {
63 value.iter().all(|item| matches!(item, JsonValue::Array(_)))
64}
65
66#[must_use]
67pub fn is_array_of_objects(value: &JsonArray) -> bool {
68 value
69 .iter()
70 .all(|item| matches!(item, JsonValue::Object(_)))
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 fn s(v: &str) -> JsonValue {
78 JsonValue::Primitive(StringOrNumberOrBoolOrNull::String(v.to_string()))
79 }
80
81 fn n(v: f64) -> JsonValue {
82 JsonValue::Primitive(StringOrNumberOrBoolOrNull::Number(v))
83 }
84
85 #[test]
86 fn normalize_primitive_passes_string_through() {
87 let v = StringOrNumberOrBoolOrNull::String("hello".into());
88 assert_eq!(
89 normalize_primitive(v),
90 StringOrNumberOrBoolOrNull::String("hello".into())
91 );
92 }
93
94 #[test]
95 fn normalize_primitive_preserves_finite_numbers() {
96 let v = StringOrNumberOrBoolOrNull::Number(3.5);
97 assert_eq!(
98 normalize_primitive(v),
99 StringOrNumberOrBoolOrNull::Number(3.5)
100 );
101 }
102
103 #[test]
104 fn normalize_primitive_turns_nan_into_null() {
105 let v = StringOrNumberOrBoolOrNull::Number(f64::NAN);
106 assert_eq!(normalize_primitive(v), StringOrNumberOrBoolOrNull::Null);
107 }
108
109 #[test]
110 fn normalize_primitive_turns_infinity_into_null() {
111 let v = StringOrNumberOrBoolOrNull::Number(f64::INFINITY);
112 assert_eq!(normalize_primitive(v), StringOrNumberOrBoolOrNull::Null);
113 let v = StringOrNumberOrBoolOrNull::Number(f64::NEG_INFINITY);
114 assert_eq!(normalize_primitive(v), StringOrNumberOrBoolOrNull::Null);
115 }
116
117 #[test]
118 fn normalize_primitive_zero_is_positive() {
119 let v = StringOrNumberOrBoolOrNull::Number(-0.0);
120 let normalized = normalize_primitive(v);
121 if let StringOrNumberOrBoolOrNull::Number(n) = normalized {
122 assert!(!n.is_sign_negative(), "expected positive zero");
123 } else {
124 panic!("expected Number variant");
125 }
126 }
127
128 #[test]
129 fn normalize_primitive_preserves_bool_and_null() {
130 assert_eq!(
131 normalize_primitive(StringOrNumberOrBoolOrNull::Bool(true)),
132 StringOrNumberOrBoolOrNull::Bool(true)
133 );
134 assert_eq!(
135 normalize_primitive(StringOrNumberOrBoolOrNull::Null),
136 StringOrNumberOrBoolOrNull::Null
137 );
138 }
139
140 #[test]
141 fn normalize_json_value_walks_array_and_replaces_nan() {
142 let v = JsonValue::Array(vec![n(1.0), n(f64::NAN), s("x")]);
143 let out = normalize_json_value(v);
144 if let JsonValue::Array(items) = out {
145 assert_eq!(items.len(), 3);
146 assert!(matches!(
147 &items[1],
148 JsonValue::Primitive(StringOrNumberOrBoolOrNull::Null)
149 ));
150 } else {
151 panic!("expected Array");
152 }
153 }
154
155 #[test]
156 fn normalize_json_value_walks_object() {
157 let v = JsonValue::Object(vec![
158 ("ok".to_string(), n(1.0)),
159 ("bad".to_string(), n(f64::INFINITY)),
160 ]);
161 let out = normalize_json_value(v);
162 if let JsonValue::Object(entries) = out {
163 assert!(matches!(
164 &entries[1].1,
165 JsonValue::Primitive(StringOrNumberOrBoolOrNull::Null)
166 ));
167 } else {
168 panic!("expected Object");
169 }
170 }
171
172 #[test]
173 fn is_json_primitive_detects_primitives() {
174 assert!(is_json_primitive(&n(1.0)));
175 assert!(!is_json_primitive(&JsonValue::Array(vec![])));
176 assert!(!is_json_primitive(&JsonValue::Object(vec![])));
177 }
178
179 #[test]
180 fn is_json_array_detects_arrays() {
181 assert!(is_json_array(&JsonValue::Array(vec![])));
182 assert!(!is_json_array(&n(1.0)));
183 }
184
185 #[test]
186 fn is_json_object_detects_objects() {
187 assert!(is_json_object(&JsonValue::Object(vec![])));
188 assert!(!is_json_object(&JsonValue::Array(vec![])));
189 }
190
191 #[test]
192 fn is_empty_object_detects_empty_and_non_empty() {
193 let empty: JsonObject = vec![];
194 let non_empty: JsonObject = vec![("a".into(), n(1.0))];
195 assert!(is_empty_object(&empty));
196 assert!(!is_empty_object(&non_empty));
197 }
198
199 #[test]
200 fn is_array_of_primitives_checks_every_item() {
201 let arr: JsonArray = vec![n(1.0), s("x")];
202 assert!(is_array_of_primitives(&arr));
203 let arr: JsonArray = vec![n(1.0), JsonValue::Array(vec![])];
204 assert!(!is_array_of_primitives(&arr));
205 }
206
207 #[test]
208 fn is_array_of_primitives_true_for_empty() {
209 let arr: JsonArray = vec![];
210 assert!(is_array_of_primitives(&arr));
211 }
212
213 #[test]
214 fn is_array_of_arrays_checks_every_item() {
215 let arr: JsonArray = vec![JsonValue::Array(vec![n(1.0)]), JsonValue::Array(vec![])];
216 assert!(is_array_of_arrays(&arr));
217 let arr: JsonArray = vec![JsonValue::Array(vec![]), n(1.0)];
218 assert!(!is_array_of_arrays(&arr));
219 }
220
221 #[test]
222 fn is_array_of_objects_checks_every_item() {
223 let arr: JsonArray = vec![JsonValue::Object(vec![])];
224 assert!(is_array_of_objects(&arr));
225 let arr: JsonArray = vec![JsonValue::Object(vec![]), n(1.0)];
226 assert!(!is_array_of_objects(&arr));
227 }
228}