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: ®ex::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: ®ex::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}