A fast, 100% Serde-compatible XML serialization and deserialization library for Rust.
- Full Serde compatibility - Works seamlessly with
#[derive(Serialize, Deserialize)] - High performance - Zero-copy parsing, SIMD-accelerated string operations via
memchr - XML Attributes - First-class support using the
@prefix convention - Rich XML support - CDATA, comments, processing instructions, and more
- Comprehensive error reporting - Line/column positions for all errors
- Minimal dependencies - Only
serde,memchr,itoa, andryu
| Operation | Throughput |
|---|---|
| Serialization (simple) | ~5.8M structs/sec |
| Serialization (complex) | ~256K structs/sec |
| Deserialization | 190-200 MiB/s |
| XML Parsing | 580+ MiB/s |
| Roundtrip (simple) | ~1.85M ops/sec |
Add to your Cargo.toml:
[dependencies]
serde-xml-fast = "0.1"
serde = { version = "1.0", features = ["derive"] }use serde::{Deserialize, Serialize};
use serde_xml::{from_str, to_string};
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Person {
name: String,
age: u32,
}
fn main() {
// Serialize to XML
let person = Person {
name: "Alice".to_string(),
age: 30,
};
let xml = to_string(&person).unwrap();
println!("{}", xml);
// Output: <Person><name>Alice</name><age>30</age></Person>
// Deserialize from XML
let xml = "<Person><name>Bob</name><age>25</age></Person>";
let person: Person = from_str(xml).unwrap();
assert_eq!(person.name, "Bob");
}Run any example with:
cargo run --example <name>Available examples:
basic- Simple serialization and deserializationnested- Nested struct handlingcollections- Vectors and collectionsattributes- XML attribute support with@prefixhtml_parsing- Parsing HTML-like structures
use serde::{Deserialize, Serialize};
use serde_xml::from_str;
#[derive(Debug, Serialize, Deserialize)]
struct Address {
city: String,
country: String,
}
#[derive(Debug, Serialize, Deserialize)]
struct Person {
name: String,
address: Address,
}
let xml = r#"
<Person>
<name>Bob</name>
<address>
<city>New York</city>
<country>USA</country>
</address>
</Person>
"#;
let person: Person = from_str(xml).unwrap();
assert_eq!(person.address.city, "New York");use serde::{Deserialize, Serialize};
use serde_xml::to_string;
#[derive(Debug, Serialize, Deserialize)]
struct Library {
book: Vec<String>,
}
let library = Library {
book: vec!["Book 1".to_string(), "Book 2".to_string()],
};
let xml = to_string(&library).unwrap();
// <Library><book>Book 1</book><book>Book 2</book></Library>Use the @ prefix to serialize/deserialize XML attributes:
use serde::{Deserialize, Serialize};
use serde_xml::{from_str, to_string};
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Item {
#[serde(rename = "@id")]
id: String, // Serializes as attribute: id="..."
#[serde(rename = "@class")]
class: String, // Serializes as attribute: class="..."
name: String, // Serializes as child element: <name>...</name>
}
// Serialize with attributes
let item = Item {
id: "123".to_string(),
class: "product".to_string(),
name: "Widget".to_string(),
};
let xml = to_string(&item).unwrap();
// Output: <Item id="123" class="product"><name>Widget</name></Item>
// Deserialize with attributes
let xml = r#"<Item id="456" class="sale"><name>Gadget</name></Item>"#;
let parsed: Item = from_str(xml).unwrap();
assert_eq!(parsed.id, "456");Use $value or $text to combine attributes with text content:
use serde::{Deserialize, Serialize};
use serde_xml::to_string;
#[derive(Serialize, Deserialize)]
struct Link {
#[serde(rename = "@href")]
href: String,
#[serde(rename = "$value")]
text: String,
}
let link = Link {
href: "https://example.com".to_string(),
text: "Click here".to_string(),
};
let xml = to_string(&link).unwrap();
// Output: <Link href="https://example.com">Click here</Link>The library can parse well-formed HTML/XHTML structures:
use serde::Deserialize;
use serde_xml::from_str;
#[derive(Deserialize)]
struct Form {
#[serde(rename = "@action")]
action: String,
#[serde(rename = "@method")]
method: Option<String>,
#[serde(default)]
input: Vec<Input>,
}
#[derive(Deserialize)]
struct Input {
#[serde(rename = "@type")]
input_type: String,
#[serde(rename = "@name")]
name: String,
}
let html = r#"
<Form action="/login" method="POST">
<input type="text" name="username"/>
<input type="password" name="password"/>
</Form>
"#;
let form: Form = from_str(html).unwrap();
assert_eq!(form.action, "/login");
assert_eq!(form.input.len(), 2);use serde::{Deserialize, Serialize};
use serde_xml::from_str;
#[derive(Debug, Serialize, Deserialize)]
struct Config {
name: String,
#[serde(default)]
value: Option<String>,
}
let xml = "<Config><name>test</name></Config>";
let config: Config = from_str(xml).unwrap();
assert_eq!(config.value, None);Special characters are automatically escaped in both content and attributes:
use serde::Serialize;
use serde_xml::to_string;
#[derive(Serialize)]
struct Element {
#[serde(rename = "@title")]
title: String,
content: String,
}
let elem = Element {
title: "Hello \"World\" & <Friends>".to_string(),
content: "<script>alert('xss')</script>".to_string(),
};
let xml = to_string(&elem).unwrap();
// Attributes: title="Hello "World" & <Friends>"
// Content: <content><script>alert('xss')</script></content>For more control, use the reader and writer directly:
use serde_xml::{XmlReader, XmlWriter, XmlEvent};
// Reading
let mut reader = XmlReader::from_str("<root>Hello</root>");
while let Ok(event) = reader.next_event() {
match event {
XmlEvent::StartElement { name, .. } => println!("Start: {}", name),
XmlEvent::Text(text) => println!("Text: {}", text),
XmlEvent::EndElement { name } => println!("End: {}", name),
XmlEvent::Eof => break,
_ => {}
}
}
// Writing
let mut writer = XmlWriter::new(Vec::new());
writer.start_element("root").unwrap();
writer.write_text("Hello").unwrap();
writer.end_element().unwrap();cargo benchcargo testCopyright 2025 Pegasus Heavy Industries LLC
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.