better_config_loader/
json.rs

1use better_config_core::{AbstractConfig, Error};
2use better_config_core::misc;
3use std::collections::HashMap;
4use std::fs;
5
6/// Indicates that structure can be initialized from JSON file.
7pub trait JsonConfig<T = HashMap<String, String>>: AbstractConfig<T> {
8    /// Load specified JSON file and initialize the structure.
9    ///
10    /// # Arguments
11    /// * `target` - Path to the JSON file.
12    ///
13    /// # Errors
14    /// * `Error::LoadFileError` - If the specified JSON file cannot be loaded or parsed.
15    fn load(target: Option<String>) -> Result<T, Error>
16    where
17        T: Default,
18        HashMap<String, String>: Into<T>,
19        Self: Sized,
20    {
21        let target = target.or(Some("config.json".to_string()));
22
23        let mut json_map = HashMap::new();
24
25        if let Some(target) = target {
26            let file_paths = misc::validate_and_split_paths(&target)?;
27
28            for file_path in file_paths {
29                // Check file accessibility before reading
30                misc::check_file_accessibility(&file_path)?;
31
32                let content = fs::read_to_string(&file_path)
33                    .map_err(|e| Error::IoError {
34                        operation: format!("read file '{}'", file_path),
35                        source: Some(Box::new(e)),
36                    })?;
37
38                let value: serde_json::Value = serde_json::from_str(&content)
39                    .map_err(|e| Error::parse_json_error(&file_path, e))?;
40
41                flatten_json_value(&value, None, &mut json_map)
42                    .map_err(|e| Error::value_conversion_error("json", "string", &format!("{}", e)))?;
43            }
44        }
45
46        Ok(json_map.into())
47    }
48}
49
50fn flatten_json_value(value: &serde_json::Value, parent_key: Option<String>, map: &mut HashMap<String, String>) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
51    match value {
52        serde_json::Value::Object(obj) => {
53            for (key, val) in obj {
54                let new_key = match &parent_key {
55                    Some(parent) => format!("{}.{}", parent, key),
56                    None => key.to_string(),
57                };
58                flatten_json_value(val, Some(new_key), map)?;
59            }
60        }
61        serde_json::Value::Array(arr) => {
62            for (i, val) in arr.iter().enumerate() {
63                let new_key = match &parent_key {
64                    Some(parent) => format!("{}[{}]", parent, i),
65                    None => i.to_string(),
66                };
67                flatten_json_value(val, Some(new_key), map)?;
68            }
69        }
70        serde_json::Value::String(s) => {
71            if let Some(key) = parent_key {
72                map.insert(key, s.to_string());
73            }
74        }
75        serde_json::Value::Number(n) => {
76            if let Some(key) = parent_key {
77                let num_str = if n.is_i64() {
78                    n.as_i64()
79                        .map(|v| v.to_string())
80                        .unwrap_or_else(|| n.to_string())
81                } else if n.is_f64() {
82                    n.as_f64()
83                        .map(|v| v.to_string())
84                        .unwrap_or_else(|| n.to_string())
85                } else {
86                    n.to_string()
87                };
88                map.insert(key, num_str);
89            }
90        }
91        serde_json::Value::Bool(b) => {
92            if let Some(key) = parent_key {
93                map.insert(key, b.to_string());
94            }
95        }
96        serde_json::Value::Null => {}
97    }
98
99    Ok(())
100}