# quickxml_to_serde Convert XML to JSON using [quick-xml](https://github.com/tafia/quick-xml) and [serde](https://github.com/serde-rs/json). Inspired by [node2object](https://github.com/vorot93/node2object). ## Usage examples #### Basic Dependencies: ```rust use std::fs::File; use std::io::prelude::*; use quickxml_to_serde::xml_string_to_json; ``` Rust code to perform a conversion: ```rust // read an XML file into a string let mut xml_file = File::open("test.xml")?; let mut xml_contents = String::new(); xml_file.read_to_string(&mut xml_contents)?; // convert the XML string into JSON with default config params let json = xml_string_to_json(xml_contents, &Config::new_with_defaults()); println!("{}", json); ``` #### Custom config The following config example changes the default behavior to: 1. Treat numbers starting with `0` as strings. E.g. `0001` will be `"0001"` 2. Do not prefix JSON properties created from attributes 3. Use `text` as the JSON property name for values of XML text nodes where the text is mixed with other nodes 4. Exclude empty elements from the output ```rust let conf = Config::new_with_custom_values(true, "", "text", NullValue::Ignore); ``` ## Enforcing JSON types ### Matching based on absolute path or regex You can override the type of absolute paths within the XML ``` rust let config = Config::new_with_defaults() .add_json_type_override("/a/b", JsonArray::Always(JsonType::AlwaysString)); ``` Or you can match based on a regex! ``` rust let config = Config::new_with_defaults() .add_json_type_override( Regex::new(r"element").unwrap(), JsonArray::Always(JsonType::Infer) ); ``` #### Strings The default for this library is to attempt to infer scalar data types, which can be `int`, `float`, `bool` or `string` in JSON. Sometimes it is not desirable like in the example below. Let's assume that attribute `id` is always numeric and can be safely converted to JSON integer. The `card_number` element looks like a number for the first two users and is a string for the third one. This inconsistency in JSON typing makes it difficult to deserialize the structure, so we may be better off telling the converter to use a particular JSON data type for some XML nodes. ```xml Andrew 000156 John 100263 Mary 100263a ``` Use `quickxml_to_serde = { version = "0.4", features = ["json_types"] }` feature in your *Cargo.toml* file to enable support for enforcing JSON types for some XML nodes using xPath-like notations. Sample XML document: ```xml true ``` Configuration to make attribute `attr1="007"` always come out as a JSON string: ```rust let conf = Config::new_with_defaults().add_json_type_override("/a/@attr1", JsonArray::Infer(JsonType::AlwaysString)); ``` Configuration to make both attributes and the text node of `` always come out as a JSON string: ```rust let conf = Config::new_with_defaults() .add_json_type_override("/a/@attr1", JsonArray::Infer(JsonType::AlwaysString)) .add_json_type_override("/a/b/@attr1", JsonArray::Infer(JsonType::AlwaysString)) .add_json_type_override("/a/b", JsonArray::Infer(JsonType::AlwaysString)); ``` #### Boolean The only two [valid boolean values in JSON](https://json-schema.org/understanding-json-schema/reference/boolean.html#boolean) are `true` and `false`. On the other hand, values such as `True`, `False`,`1` and `0` are common in programming languages and data formats. Use `JsonType::Bool(...)` type with the list of "true" values to convert arbitrary boolean values into JSON bool. ```rust let conf = Config::new_with_defaults() .add_json_type_override("/a/b", JsonArray::Infer(JsonType::Bool(vec!["True","true","1","yes"]))); ``` #### Arrays Multiple nodes with the same name are automatically converted into a JSON array. For example, ```xml 1 2 ``` is converted into ```json { "a": { "b": [1,2] } } ``` By default, a single element like ```xml 1 ``` is converted into a scalar value or a map ```json { "a": { "b": 1 } } ``` You can use `add_json_type_override()` with `JsonArray::Always()` to create a JSON array regardless of the number of elements so that `1` becomes `{ "a": { "b": [1] } }`. `JsonArray::Always()` and `JsonArray::Infer()` can specify what underlying JSON type should be used, e.g. * `JsonArray::Infer(JsonType::AlwaysString)` - infer array, convert the values to JSON string * `JsonArray::Always(JsonType::Infer)` - always wrap the values in a JSON array, infer the value types * `JsonArray::Always(JsonType::AlwaysString)` - always wrap the values in a JSON array and convert values to JSON string ```rust let config = Config::new_with_defaults() .add_json_type_override("/a/b", JsonArray::Always(JsonType::AlwaysString)); ``` Conversion of empty XML nodes like `` depends on `NullValue` setting. For example, ```rust let config = Config::new_with_custom_values(false, "@", "#text", NullValue::Ignore) .add_json_type_override("/a/b", JsonArray::Always(JsonType::Infer)); ``` converts `` to ```json {"a": null} ``` and the same `config` with `NullValue::Null` converts it to ```json {"a": { "b": [null] }} ``` It is not possible to get an empty array like `{"a": { "b": [] }}`. ---- *See embedded docs for `Config` struct and its members for more details.* ## Conversion specifics - The order of XML elements is not preserved - Namespace identifiers are dropped. E.g. `123` becomes `{ "a":123 }` - Integers and floats are converted into JSON integers and floats, unless the JSON type is specified in `Config`. - XML attributes become JSON properties at the same level as child elements. E.g. ```xml 1 ``` is converted into ```json "Test": { "Input": 1, "TestId": "0001" } ``` - XML prolog is dropped. E.g. ``. - XML namespace definitions are dropped. E.g. `` becomes `"Tests":{}` - Processing instructions, comments and DTD are ignored - **Presence of CDATA in the XML results in malformed JSON** - XML attributes can be prefixed via `Config::xml_attr_prefix`. E.g. using the default prefix `@` converts `` into `{ "a": {"@b":"y"} }`. You can use no prefix or set your own value. - Complex XML elements with text nodes put the XML text node value into a JSON property named in `Config::xml_text_node_prop_name`. E.g. setting `xml_text_node_prop_name` to `text` will convert ```xml 1234567 ``` into ```json { "CardNumber": { "Month": 3, "Year": 19, "text": 1234567 } } ``` - Elements with identical names are collected into arrays. E.g. ```xml 7.25 A 3 24.50 B 1 89.99 ``` is converted into ```json { "Root": { "Data": [ { "Category": "A", "Price": 24.5, "Quantity": 3 }, { "Category": "B", "Price": 89.99, "Quantity": 1 } ], "TaxRate": 7.25 } } ``` - If `TaxRate` element from the above example was inserted between `Data` elements it would still produce the same JSON with all `Data` properties grouped into a single array. #### Additional info and examples See [tests.rs](src/tests.rs) for more usage examples. ## Edge cases XML and JSON are not directly compatible for 1:1 conversion without additional hints to the converter. Please, post an issue if you come across any incorrect conversion.