Skip to main content

quick_xml_to_json/
lib.rs

1#![forbid(unsafe_code)]
2
3mod decoders;
4mod errors;
5mod frames;
6
7use crate::frames::AttributesWriter;
8use decoders::decode_text;
9pub use errors::XmlToJsonError;
10use quick_xml::Reader;
11use quick_xml::events::Event;
12use std::io::{BufRead, BufReader, Read, Write};
13
14static CHILDREN_KEY: &str = "#c";
15static TEXT_NODE_KEY: &str = "#t";
16static MB: usize = 1024 * 1024;
17
18/// Convert XML to JSON
19///
20/// # Example Usage
21///
22/// ```
23/// use quick_xml_to_json::xml_to_json;
24///
25/// let xml = r#"<root><parent id="1"><child>Value</child></parent></root>"#;
26/// let expected_json = serde_json::json!({
27///     "root": {
28///         "#c": [
29///             {
30///                 "parent": {
31///                     "@id": "1",
32///                     "#c": [
33///                         { "child": { "#t": "Value" } }
34///                     ]
35///                 }
36///             }
37///         ]
38///     }
39/// });
40///
41///
42/// let mut output = Vec::new();
43/// assert!(xml_to_json(xml.as_bytes(), &mut output).is_ok());
44///
45/// assert_eq!(
46///   expected_json,
47///   serde_json::from_slice::<serde_json::Value>(&output).unwrap()
48/// );
49/// ```
50///
51/// # Errors
52///
53/// This may error when:
54///
55/// * reading XML
56/// * serializing strings to JSON
57/// * converting a String to a byte array
58/// * writing to the buffer
59pub fn xml_to_json<R: Read, W: Write>(reader: R, out: W) -> Result<(), XmlToJsonError> {
60    xml_to_json_from_bufread(BufReader::new(reader), out)
61}
62
63/// Convert XML to JSON from a buffered reader.
64///
65/// Use this instead of [`xml_to_json`] when the input is already buffered (e.g. `BufReader`,
66/// `Cursor`, or an in-memory byte slice via `std::io::Cursor`) to avoid double-buffering.
67///
68/// # Errors
69///
70/// This may error when:
71///
72/// * reading XML
73/// * serializing strings to JSON
74/// * converting a String to a byte array
75/// * writing to the buffer
76pub fn xml_to_json_from_bufread<R: BufRead, W: Write>(
77    reader: R,
78    out: W,
79) -> Result<(), XmlToJsonError> {
80    let mut writer = std::io::BufWriter::with_capacity(MB * 2, out);
81    let mut xml = Reader::from_reader(reader);
82    xml.config_mut().trim_text(true);
83    let mut buf = Vec::with_capacity(256);
84    let mut stack: Vec<frames::Element> = Vec::with_capacity(16);
85    let mut spare_text_buf = String::new();
86
87    loop {
88        match (xml.read_event_into(&mut buf)?, stack.last_mut()) {
89            // # Process root element
90            //
91            // Open root element that has children
92            (Event::Start(e), None) => {
93                let text_buf = std::mem::take(&mut spare_text_buf);
94                let mut frame = frames::Element::new_and_open(&e, &xml, &mut writer, text_buf)?;
95                frame.process_element_attributes(&e, &xml, &mut writer)?;
96
97                stack.push(frame);
98            }
99
100            // Open root that has no children
101            (Event::Empty(e), None) => {
102                let mut frame = frames::EmptyNode::new_and_open(&e, &xml, &mut writer)?;
103                frame.process_element_attributes(&e, &xml, &mut writer)?;
104                frame.close(&mut writer)?;
105
106                writer.flush()?;
107                return Ok(());
108            }
109
110            // # Process child element
111            //
112            // Open child element that has children
113            (Event::Start(e), Some(parent)) => {
114                parent.begin_child(&mut writer)?;
115
116                let text_buf = std::mem::take(&mut spare_text_buf);
117                let mut frame = frames::Element::new_and_open(&e, &xml, &mut writer, text_buf)?;
118                frame.process_element_attributes(&e, &xml, &mut writer)?;
119
120                stack.push(frame);
121            }
122
123            // Open child element that has no children
124            (Event::Empty(e), Some(parent)) => {
125                parent.begin_child(&mut writer)?;
126
127                let mut frame = frames::EmptyNode::new_and_open(&e, &xml, &mut writer)?;
128                frame.process_element_attributes(&e, &xml, &mut writer)?;
129                frame.close(&mut writer)?;
130            }
131
132            // Process a text node of an element
133            (Event::Text(t), Some(frame)) => {
134                let text = decode_text(&xml, &t)?;
135                frame.push_text(&text);
136            }
137
138            // Close out the current node on the stack
139            (Event::End(_), _) => {
140                if let Some(mut frame) = stack.pop() {
141                    frame.close(&mut writer)?;
142                    spare_text_buf = frame.take_text_buf();
143
144                    // If there's nothing else on the stack, we're done
145                    if stack.is_empty() {
146                        writer.flush()?;
147                        return Ok(());
148                    }
149                }
150            }
151
152            (Event::Eof, _) => break,
153            _ => {}
154        }
155
156        buf.clear();
157    }
158
159    Err(XmlToJsonError::InvalidXML)
160}
161
162#[cfg(test)]
163mod tests {
164
165    #[test]
166    fn test_nested_structure() {
167        let xml = r#"
168            <root>
169                <child1 attr1="value1">
170                    <subchild>Text 1</subchild>
171                    <subchild>Text 2</subchild>
172                    <subchild>Text 3</subchild>
173                </child1>
174                <child2 attr2="value2" />
175                <child1 attr2="value2" attr3="value3" attr1="value1">
176                    <subchild>Text 2</subchild>
177                    <subchild>Text 1</subchild>
178                    <subchild>Text 3</subchild>
179                </child1>
180            </root>
181        "#;
182
183        let expected_json = serde_json::json!({
184            "root": {
185                "#c": [
186                    {
187                        "child1": {
188                            "@attr1": "value1",
189                            "#c": [
190                                {
191                                    "subchild": {
192                                        "#t": "Text 1"
193                                    }
194                                },
195                                {
196                                    "subchild": {
197                                        "#t": "Text 2"
198                                    }
199                                },
200                                {
201                                    "subchild": {
202                                        "#t": "Text 3"
203                                    }
204                                },
205                            ]
206                        }
207                    },
208                    {
209                        "child2": {
210                            "@attr2": "value2"
211                        }
212                    },
213                    {
214                        "child1": {
215                            "@attr3": "value3",
216                            "@attr2": "value2",
217                            "@attr1": "value1",
218                            "#c": [
219                                {
220                                    "subchild": {
221                                        "#t": "Text 2"
222                                    }
223                                },
224                                {
225                                    "subchild": {
226                                        "#t": "Text 1"
227                                    }
228                                },
229                                {
230                                    "subchild": {
231                                        "#t": "Text 3"
232                                    }
233                                },
234                            ]
235                        }
236                    },
237                ]
238            }
239        });
240
241        assert_eq!(expected_json, convert_xml_to_json(xml));
242    }
243
244    #[test]
245    fn test_basic_xml_to_json() {
246        let xml = r#"<users count="3">
247  <user age="40">Jane Doe</user>
248  <user age="42">John Doe</user>
249  <user age="12">Jim Doe</user>
250</users>"#;
251
252        let expected_json = serde_json::json!({
253            "users": {
254                "@count": "3",
255                "#c": [
256                    {
257                        "user": {
258                            "@age": "40",
259                            "#t": "Jane Doe"
260                        }
261                    },
262                    {
263                        "user": {
264                            "@age": "42",
265                            "#t": "John Doe"
266                        }
267                    },
268                    {
269                        "user": {
270                            "@age": "12",
271                            "#t": "Jim Doe"
272                        }
273                    }
274                ]
275            }
276        });
277
278        assert_eq!(expected_json, convert_xml_to_json(xml));
279    }
280
281    #[test]
282    fn test_single_element_with_text() {
283        let xml = "<name>John Doe</name>";
284        let expected_json = serde_json::json!({
285            "name": {
286                "#t": "John Doe"
287            }
288        });
289
290        assert_eq!(expected_json, convert_xml_to_json(xml));
291    }
292
293    #[test]
294    fn test_element_with_attributes_only() {
295        let xml = r#"<div class="container" id="main"></div>"#;
296        let expected_json = serde_json::json!({
297            "div": {
298                "@class": "container",
299                "@id": "main"
300            }
301        });
302
303        assert_eq!(expected_json, convert_xml_to_json(xml));
304    }
305
306    #[test]
307    fn test_nested_elements() {
308        let xml = r#"<root><parent id="1"><child>Value</child></parent></root>"#;
309        let expected_json = serde_json::json!({
310            "root": {
311                "#c": [
312                    {
313                        "parent": {
314                            "@id": "1",
315                            "#c": [
316                                { "child": { "#t": "Value" } }
317                            ]
318                        }
319                    }
320                ]
321            }
322        });
323
324        assert_eq!(expected_json, convert_xml_to_json(xml));
325    }
326
327    #[test]
328    fn test_empty_xml() {
329        assert!(super::xml_to_json(b"".as_slice(), Vec::new()).is_err());
330    }
331
332    #[test]
333    fn test_malformed_xml() {
334        assert!(super::xml_to_json(b"<root><unclosed>".as_slice(), Vec::new()).is_err());
335    }
336
337    #[test]
338    fn test_empty_nodes() {
339        let xml = "<main><br /></main>";
340        let expected_json = serde_json::json!({
341            "main": {
342                "#c": [
343                    { "br": {}}
344                ]
345            }
346        });
347
348        assert_eq!(expected_json, convert_xml_to_json(xml));
349    }
350
351    #[test]
352    fn test_empty_node_at_root() {
353        let xml = "<br />";
354        let expected_json = serde_json::json!({ "br": {} });
355
356        assert_eq!(expected_json, convert_xml_to_json(xml));
357    }
358
359    #[test]
360    fn test_empty_node_at_root_with_attributes() {
361        let xml = r#"<br id="5" />"#;
362        let expected_json = serde_json::json!({ "br": { "@id": "5" } });
363
364        assert_eq!(expected_json, convert_xml_to_json(xml));
365    }
366
367    fn convert_xml_to_json(xml: &str) -> serde_json::Value {
368        let mut output = Vec::new();
369        super::xml_to_json(xml.as_bytes(), &mut output).unwrap();
370
371        serde_json::from_slice(&output).unwrap()
372    }
373}