Skip to main content

anomalyx_normalize/parsers/
json.rs

1//! JSON document parser: an array of records, a lone object (one row), or a
2//! scalar/array (one `value` cell).
3
4use 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        // A leading `[` or `{` is a JSON document. NDJSON outranks this when it
22        // sees repeated object-lines (see ndjson.rs).
23        (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}