jom/
lib.rs

1use regex::Regex;
2use serde_json::{self, Value};
3
4/// Helper function to get nested JSON value for a given dot-separated key.
5///
6/// For example, given the key `"phones.0"`, it will attempt to access
7/// `json["phones"]["0"]`.
8fn get_nested_value<'a>(json: &'a Value, key: &str) -> Option<&'a Value> {
9    let mut value = json;
10    for k in key.split('.') {
11        value = value.get(k)?;
12    }
13    Some(value)
14}
15
16/// Converts JSON data to markdown by replacing placeholders in the markdown template
17/// with values extracted from the JSON data. Placeholders should be in the form `{key}`
18/// or `{nested.key}`.
19///
20/// # Arguments
21///
22/// * `json_data` - A string slice containing the JSON data.
23/// * `markdown` - A string slice containing the markdown template.
24///
25/// # Returns
26///
27/// A `Result` containing the rendered markdown as a `String` or a `serde_json::Error`.
28///
29/// # Example
30///
31/// ```rust
32/// use jom::json_to_markdown;
33///
34/// let json_data = r#"
35///     {
36///         "name": "John Doe",
37///         "age": 43,
38///         "contact": ["+4400000000", "john@doe.com"],
39///         "skills": {
40///             "languages": ["Rust", "Python", "JavaScript"],
41///             "tools": ["Git", "Docker", "Kubernetes"]
42///         }
43///     }"#;
44///
45/// // markdown example
46/// let markdown = r#"
47///     # {name}
48///     ## {age}
49///     ### Contact Details
50///     ...{contact}
51///     ### Skills
52///     #### Languages
53///     ...{skills.languages}
54///     #### Tools
55///     ...{skills.tools}
56/// "#;
57///
58/// let rendered = json_to_markdown(json_data, markdown).unwrap();
59/// println!("{}", rendered);
60/// ```
61///
62pub fn json_to_markdown(json_data: &str, markdown: &str) -> serde_json::Result<String> {
63    // Parse the JSON string into a serde_json::Value.
64    let json: Value = serde_json::from_str(json_data)?;
65
66    // Compile the regex to capture placeholders like {key} or {nested.key}.
67    let re = Regex::new(r"\{([a-zA-Z0-9_.]+)\}").unwrap();
68
69    // Regular expression for destructor of json array `...{key}`.
70    let re_array = Regex::new(r"\...\{([a-zA-Z0-9_.]+)\}").unwrap();
71
72    let string_one = re_array.replace_all(markdown, |caps: &regex::Captures| {
73        // Extract the key name from the captured group.
74        let key = caps.get(1).unwrap().as_str();
75        // Look up the key in the JSON. If not found, leave the original placeholder.
76        match get_nested_value(&json, key) {
77            // Prepend the value with '-' to make it a markdown list item.
78            Some(value) => {
79                let mut rendered = String::new();
80
81                if value.is_array() {
82                    for (_, v) in value.as_array().unwrap().iter().enumerate() {
83                        rendered.push_str(&format!("- {}\n", v).replace("\"", ""));
84                    }
85                }
86
87                if value.is_object() {
88                    for (k, v) in value.as_object().unwrap().iter() {
89                        rendered.push_str(&format!("- {}: {}\n", k, v).replace("\"", ""));
90                    }
91                }
92
93                rendered
94            }
95            None => caps.get(0).unwrap().as_str().to_string(),
96        }
97    });
98
99    // Replace each placeholder with the corresponding value from the JSON.
100    let rendered = re.replace_all(&string_one, |caps: &regex::Captures| {
101        // Extract the key name from the captured group.
102        let key = caps.get(1).unwrap().as_str();
103        // Look up the key in the JSON. If not found, leave the original placeholder.
104        match get_nested_value(&json, key) {
105            Some(value) => value.to_string().replace("\"", ""),
106            None => caps.get(0).unwrap().as_str().to_string(),
107        }
108    });
109
110    Ok(rendered.to_string())
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    #[test]
118    fn test_json_to_markdown() {
119        // JSON data example
120        let json_data = r#"
121            {
122                "name": "John Doe",
123                "age": 43,
124                "contact": ["+44 1234567", "+44 2345678"]
125            }"#;
126
127        // markdown example
128        let markdown = r#"
129            # {name}
130            ## {age}
131            ### Contact Details
132            ...{contact}
133        "#;
134
135        let rendered = json_to_markdown(json_data, markdown).unwrap();
136
137        // Ensure the output contains the expected values.
138        assert!(rendered.contains("John Doe"));
139        assert!(rendered.contains("43"));
140        assert!(rendered.contains("+44 1234567"));
141        assert!(rendered.contains("+44 2345678"));
142    }
143}