cs/parse/
json_parser.rs

1use crate::error::{Result, SearchError};
2use serde_json::Value;
3use std::fs;
4use std::path::{Path, PathBuf};
5
6use super::translation::TranslationEntry;
7
8/// Parser for JSON translation files
9pub struct JsonParser;
10
11impl JsonParser {
12    pub fn parse_file(path: &Path) -> Result<Vec<TranslationEntry>> {
13        let content = fs::read_to_string(path).map_err(|e| {
14            SearchError::yaml_parse_error(path, format!("Failed to read file: {}", e))
15        })?;
16
17        let root: Value = serde_json::from_str(&content).map_err(|e| {
18            SearchError::yaml_parse_error(path, format!("Invalid JSON syntax: {}", e))
19        })?;
20
21        let mut entries = Vec::new();
22        Self::flatten_json(&root, String::new(), path, &mut entries);
23
24        Ok(entries)
25    }
26
27    fn flatten_json(
28        value: &Value,
29        prefix: String,
30        file_path: &Path,
31        entries: &mut Vec<TranslationEntry>,
32    ) {
33        match value {
34            Value::Object(map) => {
35                for (key, val) in map {
36                    let new_prefix = if prefix.is_empty() {
37                        key.clone()
38                    } else {
39                        format!("{}.{}", prefix, key)
40                    };
41
42                    Self::flatten_json(val, new_prefix, file_path, entries);
43                }
44            }
45            Value::String(s) => {
46                // Note: JSON doesn't easily give us line numbers with standard serde_json
47                // For now, we'll use 0 as the line number or try to find it if we want to be fancy later
48                // To keep it simple and consistent with the requirement, we'll just store the entry.
49                // If line numbers are critical, we might need a different parser or a second pass.
50                // Given the constraints, 0 is acceptable for MVP JSON support if line numbers are hard.
51                // However, let's try to be better. We can't easily get line numbers from serde_json::Value.
52                // We will accept 0 for now as a limitation of serde_json default deserialization.
53                entries.push(TranslationEntry {
54                    key: prefix,
55                    value: s.clone(),
56                    line: 0, // Placeholder
57                    file: PathBuf::from(file_path),
58                });
59            }
60            Value::Number(n) => {
61                entries.push(TranslationEntry {
62                    key: prefix,
63                    value: n.to_string(),
64                    line: 0,
65                    file: PathBuf::from(file_path),
66                });
67            }
68            Value::Bool(b) => {
69                entries.push(TranslationEntry {
70                    key: prefix,
71                    value: b.to_string(),
72                    line: 0,
73                    file: PathBuf::from(file_path),
74                });
75            }
76            _ => {
77                // Ignore arrays and nulls for now
78            }
79        }
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use std::io::Write;
87    use tempfile::NamedTempFile;
88
89    #[test]
90    fn test_parse_simple_json() {
91        let mut file = NamedTempFile::new().unwrap();
92        write!(file, r#"{{"key": "value"}}"#).unwrap();
93
94        let entries = JsonParser::parse_file(file.path()).unwrap();
95        assert_eq!(entries.len(), 1);
96        assert_eq!(entries[0].key, "key");
97        assert_eq!(entries[0].value, "value");
98    }
99
100    #[test]
101    fn test_parse_nested_json() {
102        let mut file = NamedTempFile::new().unwrap();
103        write!(file, r#"{{"parent": {{"child": "value"}}}}"#).unwrap();
104
105        let entries = JsonParser::parse_file(file.path()).unwrap();
106        assert_eq!(entries.len(), 1);
107        assert_eq!(entries[0].key, "parent.child");
108        assert_eq!(entries[0].value, "value");
109    }
110}