# 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
Andrew000156John100263Mary100263a
```
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
12
```
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.25A324.50B189.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.