anomalyx_normalize/parsers/
json.rs1use crate::parser::{Confidence, FormatParser, TEXT};
5use crate::table::TableBuilder;
6use ax_core::{AxError, Column};
7
8#[derive(Debug, Default, Clone)]
9pub struct JsonParser;
10
11impl FormatParser for JsonParser {
12 fn id(&self) -> &'static str {
13 "json"
14 }
15 fn extensions(&self) -> &'static [&'static str] {
16 &["json"]
17 }
18 fn sniff(&self, bytes: &[u8]) -> Option<Confidence> {
19 let text = std::str::from_utf8(bytes).ok()?;
20 let first = text.trim_start().chars().next()?;
21 (first == '[' || first == '{').then_some(TEXT)
24 }
25 fn parse(&self, _source: &str, bytes: &[u8]) -> Result<Vec<Column>, AxError> {
26 let val: serde_json::Value = serde_json::from_slice(bytes).map_err(|e| AxError::Parse {
27 format: self.id().to_string(),
28 message: e.to_string(),
29 })?;
30 let mut builder = TableBuilder::new();
31 match val {
32 serde_json::Value::Array(items) => {
33 for item in items {
34 builder.push_value(item);
35 }
36 }
37 other => builder.push_value(other),
38 }
39 Ok(builder.finish())
40 }
41}
42
43#[cfg(test)]
44mod tests {
45 use super::*;
46 use ax_core::ColType;
47
48 #[test]
49 fn array_of_objects() {
50 let cols = JsonParser
51 .parse("d.json", br#"[{"x":10},{"x":20},{"x":30}]"#)
52 .unwrap();
53 assert_eq!(cols.len(), 1);
54 assert_eq!(cols[0].name, "x");
55 assert_eq!(cols[0].ty, ColType::Int);
56 assert_eq!(cols[0].len(), 3);
57 }
58
59 #[test]
60 fn scalar_array_goes_to_value_column() {
61 let cols = JsonParser.parse("d.json", b"[1,2,3]").unwrap();
62 assert_eq!(cols[0].name, "value");
63 assert_eq!(cols[0].numeric(), vec![1.0, 2.0, 3.0]);
64 }
65
66 #[test]
67 fn sniff_recognizes_json_shapes() {
68 assert_eq!(JsonParser.sniff(b"[1,2]"), Some(TEXT));
69 assert_eq!(JsonParser.sniff(b" {\"a\":1}"), Some(TEXT));
70 assert_eq!(JsonParser.sniff(b"a,b"), None);
71 }
72
73 #[test]
74 fn malformed_json_errors() {
75 assert!(matches!(
76 JsonParser.parse("d.json", b"{not json"),
77 Err(AxError::Parse { .. })
78 ));
79 }
80}