1use crate::stats::FieldStats;
2use serde_json::Value;
3use std::collections::HashMap;
4
5pub struct JsonAnalyzer {
6 stats: HashMap<String, FieldStats>,
7 total_samples: u64,
8}
9
10impl Default for JsonAnalyzer {
11 fn default() -> Self {
12 Self::new()
13 }
14}
15
16impl JsonAnalyzer {
17 pub fn new() -> Self {
18 Self {
19 stats: HashMap::new(),
20 total_samples: 0,
21 }
22 }
23
24 pub fn analyze(&mut self, value: &Value) {
26 self.total_samples += 1;
27 self.walk("", value, 0);
28 }
29
30 fn walk(&mut self, path: &str, value: &Value, depth: usize) {
32 match value {
33 Value::Object(map) => {
34 for (key, val) in map {
35 let field_path = if path.is_empty() {
36 key.clone()
37 } else {
38 format!("{}.{}", path, key)
39 };
40
41 self.record_field(&field_path, val, depth + 1);
42
43 self.walk(&field_path, val, depth + 1);
44 }
45 }
46 Value::Array(arr) => {
47 let array_path = format!("{}[]", path);
48
49 for item in arr {
50 self.walk(&array_path, item, depth + 1);
51 }
52 }
53 _ => {
54 }
56 }
57 }
58
59 fn record_field(&mut self, path: &str, value: &Value, depth: usize) {
60 self.stats
61 .entry(path.to_string())
62 .or_insert_with(|| FieldStats::new(path.to_string(), depth))
63 .record(value);
64 }
65
66 pub fn finalize(mut self) -> HashMap<String, FieldStats> {
67 for stats in self.stats.values_mut() {
68 stats.finalize(self.total_samples);
69 }
70 self.stats
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77 use crate::types::JsonType;
78 use serde_json::json;
79
80 #[test]
81 fn test_flat_object() {
82 let mut analyzer = JsonAnalyzer::new();
83
84 analyzer.analyze(&json!({
85 "name": "Alice",
86 "age": 30
87 }));
88
89 let stats = analyzer.finalize();
90
91 assert_eq!(stats.len(), 2);
92 assert!(stats.contains_key("name"));
93 assert!(stats.contains_key("age"));
94
95 let name_stats = &stats["name"];
96 assert_eq!(name_stats.occurrences, 1);
97 assert_eq!(name_stats.density, 1.0);
98 assert_eq!(name_stats.depth, 1);
99 }
100
101 #[test]
102 fn test_nested_object() {
103 let mut analyzer = JsonAnalyzer::new();
104
105 analyzer.analyze(&json!({
106 "user": {
107 "profile": {
108 "email": "test@example.com"
109 }
110 }
111 }));
112
113 let stats = analyzer.finalize();
114
115 assert!(stats.contains_key("user"));
116 assert!(stats.contains_key("user.profile"));
117 assert!(stats.contains_key("user.profile.email"));
118
119 assert_eq!(stats["user.profile.email"].depth, 3);
120 }
121
122 #[test]
123 fn test_array_with_objects() {
124 let mut analyzer = JsonAnalyzer::new();
125
126 analyzer.analyze(&json!({
127 "addresses": [
128 {"city": "Brisbane", "zip": "4000"},
129 {"city": "Sydney", "zip": "2000"}
130 ]
131 }));
132
133 let stats = analyzer.finalize();
134
135 assert!(stats.contains_key("addresses"));
137 assert!(stats.contains_key("addresses[].city"));
138 assert!(stats.contains_key("addresses[].zip"));
139
140 let city_stats = &stats["addresses[].city"];
142 assert_eq!(city_stats.occurrences, 2); }
144
145 #[test]
146 fn test_multiple_documents_density() {
147 let mut analyzer = JsonAnalyzer::new();
148
149 analyzer.analyze(&json!({"name": "Alice", "nickname": "Al"}));
151 analyzer.analyze(&json!({"name": "Bob"}));
152 analyzer.analyze(&json!({"name": "Carol"}));
153
154 let stats = analyzer.finalize();
155
156 let name_stats = &stats["name"];
157 assert_eq!(name_stats.occurrences, 3);
158 assert_eq!(name_stats.density, 1.0); let nickname_stats = &stats["nickname"];
161 assert_eq!(nickname_stats.occurrences, 1);
162 assert!((nickname_stats.density - 0.333).abs() < 0.01); }
164
165 #[test]
166 fn test_type_inconsistency() {
167 let mut analyzer = JsonAnalyzer::new();
168
169 analyzer.analyze(&json!({"age": "25"})); analyzer.analyze(&json!({"age": 30})); analyzer.analyze(&json!({"age": "35"})); let stats = analyzer.finalize();
174 let age_stats = &stats["age"];
175
176 assert_eq!(age_stats.types.get(&JsonType::String), Some(&2));
178 assert_eq!(age_stats.types.get(&JsonType::Number), Some(&1));
179 }
180
181 #[test]
182 fn test_null_tracking() {
183 let mut analyzer = JsonAnalyzer::new();
184
185 analyzer.analyze(&json!({"optional": null}));
186 analyzer.analyze(&json!({"optional": "value"}));
187 analyzer.analyze(&json!({"optional": null}));
188
189 let stats = analyzer.finalize();
190 let optional_stats = &stats["optional"];
191
192 assert_eq!(optional_stats.null_count, 2);
193 assert_eq!(optional_stats.occurrences, 3);
194 }
195
196 #[test]
197 fn test_empty_array() {
198 let mut analyzer = JsonAnalyzer::new();
199
200 analyzer.analyze(&json!({"items": []}));
201
202 let stats = analyzer.finalize();
203
204 assert!(stats.contains_key("items"));
206
207 assert!(!stats.contains_key("items[]"));
209 }
210
211 #[test]
212 fn test_deep_nesting() {
213 let mut analyzer = JsonAnalyzer::new();
214
215 analyzer.analyze(&json!({
216 "level1": {
217 "level2": {
218 "level3": {
219 "level4": {
220 "value": "deep"
221 }
222 }
223 }
224 }
225 }));
226
227 let stats = analyzer.finalize();
228
229 assert_eq!(stats["level1"].depth, 1);
230 assert_eq!(stats["level1.level2"].depth, 2);
231 assert_eq!(stats["level1.level2.level3"].depth, 3);
232 assert_eq!(stats["level1.level2.level3.level4"].depth, 4);
233 assert_eq!(stats["level1.level2.level3.level4.value"].depth, 5);
234 }
235
236 #[test]
237 fn test_examples_collection() {
238 let mut analyzer = JsonAnalyzer::new();
239
240 for i in 0..15 {
241 analyzer.analyze(&json!({"value": i}));
242 }
243
244 let stats = analyzer.finalize();
245 let value_stats = &stats["value"];
246
247 assert_eq!(value_stats.examples.len(), 10);
249 }
250}