| Crates.io | facet-xml |
| lib.rs | facet-xml |
| version | 0.43.1 |
| created_at | 2025-12-11 16:53:03.170685+00 |
| updated_at | 2026-01-23 08:49:51.179243+00 |
| description | XML serialization for facet using the new format architecture - successor to facet-xml |
| homepage | https://facet.rs |
| repository | https://github.com/facet-rs/facet |
| max_upload_size | |
| id | 1980103 |
| size | 245,040 |
XML serialization and deserialization for Rust using the facet reflection framework.
The XML serializer and deserializer assumes every node is lowerCamelCase, unless explicitly renamed.
# use facet::Facet;
# use facet_xml as xml;
#[derive(Facet, Debug)]
struct Banana {
taste: String,
}
# let xml_str = "<banana><taste>sweet</taste></banana>";
# let b: Banana = facet_xml::from_str(xml_str).unwrap();
# assert_eq!(b.taste, "sweet");
Use rename to override:
# use facet::Facet;
# use facet_xml as xml;
#[derive(Facet, Debug)]
#[facet(rename = "Banana")]
struct Banana {
taste: String,
}
# let xml_str = "<Banana><taste>sweet</taste></Banana>";
# let b: Banana = facet_xml::from_str(xml_str).unwrap();
# assert_eq!(b.taste, "sweet");
By default, fields are matched against child elements with the same name (in lowerCamelCase).
<person>
<name>Ella</name>
<age>42</age>
</person>
# use facet::Facet;
# use facet_xml as xml;
#[derive(Facet, Debug)]
struct Person {
name: String, // captures "Ella"
age: u32, // captures 42
}
# let xml_str = "<person><name>Ella</name><age>42</age></person>";
# let person: Person = facet_xml::from_str(xml_str).unwrap();
# assert_eq!(person.name, "Ella");
# assert_eq!(person.age, 42);
Use xml::attribute to capture XML attributes:
<link href="/home">Home</link>
# use facet::Facet;
# use facet_xml as xml;
#[derive(Facet, Debug)]
struct Link {
#[facet(xml::attribute)]
href: String, // captures "/home"
#[facet(xml::text)]
text: String, // captures "Home"
}
# let xml_str = r#"<link href="/home">Home</link>"#;
# let link: Link = facet_xml::from_str(xml_str).unwrap();
# assert_eq!(link.href, "/home");
# assert_eq!(link.text, "Home");
Use xml::text to capture text content:
<name>Ella</name>
# use facet::Facet;
# use facet_xml as xml;
#[derive(Facet, Debug)]
struct Name {
#[facet(xml::text)]
value: String, // captures "Ella"
}
# let xml_str = "<name>Ella</name>";
# let name: Name = facet_xml::from_str(xml_str).unwrap();
# assert_eq!(name.value, "Ella");
For list types (Vec, etc.), facet-xml collects items. By default, items are child elements with the singularized field name (via facet-singularize).
<playlist>
<track>Song A</track>
<track>Song B</track>
</playlist>
# use facet::Facet;
# use facet_xml as xml;
#[derive(Facet, Debug)]
struct Playlist {
tracks: Vec<String>, // "tracks" → expects <track> elements
}
# let xml_str = "<playlist><track>Song A</track><track>Song B</track></playlist>";
# let playlist: Playlist = facet_xml::from_str(xml_str).unwrap();
# assert_eq!(playlist.tracks, vec!["Song A", "Song B"]);
rename# use facet::Facet;
# use facet_xml as xml;
#[derive(Facet, Debug)]
struct Playlist {
#[facet(rename = "song")]
tracks: Vec<String>, // expects <song> instead of <track>
}
# let xml_str = "<playlist><song>Song A</song><song>Song B</song></playlist>";
# let playlist: Playlist = facet_xml::from_str(xml_str).unwrap();
# assert_eq!(playlist.tracks, vec!["Song A", "Song B"]);
xml::elements (same as default)# use facet::Facet;
# use facet_xml as xml;
#[derive(Facet, Debug)]
struct Playlist {
#[facet(xml::elements)]
tracks: Vec<String>,
}
# let xml_str = "<playlist><track>Song A</track><track>Song B</track></playlist>";
# let playlist: Playlist = facet_xml::from_str(xml_str).unwrap();
# assert_eq!(playlist.tracks, vec!["Song A", "Song B"]);
renameWhen collecting struct items, use rename to specify the element name. The rename overrides
the default singularized field name:
# use facet::Facet;
# use facet_xml as xml;
#[derive(Facet, Debug, PartialEq)]
struct Person {
#[facet(xml::attribute)]
name: String,
}
#[derive(Facet, Debug)]
struct Team {
// "individual" instead of default "member" (singularized from "members")
#[facet(xml::elements, rename = "individual")]
members: Vec<Person>,
}
# let xml_str = r#"<team><individual name="Alice"/><individual name="Bob"/></team>"#;
# let team: Team = facet_xml::from_str(xml_str).unwrap();
# assert_eq!(team.members.len(), 2);
# assert_eq!(team.members[0].name, "Alice");
xml::text<message>Hello <b>world</b>!</message>
# use facet::Facet;
# use facet_xml as xml;
#[derive(Facet, Debug)]
struct Message {
#[facet(xml::text)]
parts: Vec<String>, // collects text nodes: ["Hello ", "!"]
}
# let xml_str = "<message>Hello world!</message>";
# let msg: Message = facet_xml::from_str(xml_str).unwrap();
# assert_eq!(msg.parts, vec!["Hello world!"]);
xml::attribute<element foo="1" bar="2" baz="3"/>
# use facet::Facet;
# use facet_xml as xml;
#[derive(Facet, Debug)]
#[facet(rename = "element")]
struct Element {
#[facet(xml::attribute)]
values: Vec<String>, // collects all attribute values
}
# let xml_str = r#"<element foo="1" bar="2" baz="3"/>"#;
# let elem: Element = facet_xml::from_str(xml_str).unwrap();
# assert_eq!(elem.values, vec!["1", "2", "3"]);
When you have a Vec<SomeEnum> and want each enum variant to appear directly as a child element
(without a wrapper), use #[facet(flatten)]:
<canvas>
<circle r="5"/>
<rect width="10" height="20"/>
<path d="M0 0 L10 10"/>
</canvas>
# use facet::Facet;
# use facet_xml as xml;
#[derive(Facet, Debug, PartialEq)]
#[repr(u8)]
enum Shape {
Circle {
#[facet(xml::attribute)]
r: f64
},
Rect {
#[facet(xml::attribute)]
width: f64,
#[facet(xml::attribute)]
height: f64
},
Path {
#[facet(xml::attribute)]
d: String
},
}
#[derive(Facet, Debug)]
struct Canvas {
#[facet(flatten)]
children: Vec<Shape>, // collects <circle>, <rect>, <path> directly
}
# let xml_str = r#"<canvas><circle r="5"/><rect width="10" height="20"/><path d="M0 0 L10 10"/></canvas>"#;
# let canvas: Canvas = facet_xml::from_str(xml_str).unwrap();
# assert_eq!(canvas.children.len(), 3);
Without #[facet(flatten)], the field would expect wrapper elements:
<!-- Without flatten: expects <child> wrappers -->
<canvas>
<child><circle r="5"/></child>
<child><rect width="10" height="20"/></child>
</canvas>
This pattern is essential for XML formats like SVG, HTML, or any schema where parent elements contain heterogeneous children identified by their element names.
Tuples are treated like lists: each element becomes a child element with the field's name (or singularized name for plural field names). Elements are matched by position.
<record>
<value>42</value>
<value>hello</value>
<value>true</value>
</record>
# use facet::Facet;
# use facet_xml as xml;
#[derive(Facet, Debug, PartialEq)]
#[facet(rename = "record")]
struct Record {
#[facet(rename = "value")]
data: (i32, String, bool),
}
# let xml_str = "<record><value>42</value><value>hello</value><value>true</value></record>";
# let record: Record = facet_xml::from_str(xml_str).unwrap();
# assert_eq!(record.data, (42, "hello".to_string(), true));
Without rename, the field name is used directly (no singularization for tuples since tuple fields typically have singular names):
# use facet::Facet;
# use facet_xml as xml;
#[derive(Facet, Debug, PartialEq)]
#[facet(rename = "record")]
struct Record {
data: (i32, String, bool),
}
# let xml_str = "<record><data>42</data><data>hello</data><data>true</data></record>";
# let record: Record = facet_xml::from_str(xml_str).unwrap();
# assert_eq!(record.data, (42, "hello".to_string(), true));
In XML, enums are always treated as externally tagged - the element name is the variant discriminator. This is natural for XML because the element structure already provides tagging.
Any #[facet(tag = "...")] or #[facet(content = "...")] attributes are ignored for XML
serialization. These attributes are useful for JSON (which needs explicit tag fields), but
XML doesn't need them since element names serve this purpose.
Unit variants become empty elements:
# use facet::Facet;
#[derive(Facet, Debug, PartialEq)]
#[repr(u8)]
enum Status {
Active,
Inactive,
}
// "Active" becomes <active> (lowerCamelCase)
# let xml_str = "<active/>";
# let status: Status = facet_xml::from_str(xml_str).unwrap();
# assert_eq!(status, Status::Active);
Newtype variants (single unnamed field) wrap their content:
# use facet::Facet;
#[derive(Facet, Debug, PartialEq)]
#[repr(u8)]
enum Value {
Text(String),
Number(i32),
}
// <text>hello</text> deserializes to Value::Text("hello")
# let xml_str = "<text>hello</text>";
# let value: Value = facet_xml::from_str(xml_str).unwrap();
# assert_eq!(value, Value::Text("hello".into()));
Struct variants have child elements for their fields:
# use facet::Facet;
#[derive(Facet, Debug, PartialEq)]
#[repr(u8)]
enum Shape {
Circle { radius: f64 },
Rectangle { width: f64, height: f64 },
}
// <circle><radius>5.0</radius></circle>
# let xml_str = "<circle><radius>5.0</radius></circle>";
# let shape: Shape = facet_xml::from_str(xml_str).unwrap();
# assert_eq!(shape, Shape::Circle { radius: 5.0 });
Use #[facet(rename = "...")] on variants to override the element name:
# use facet::Facet;
#[derive(Facet, Debug, PartialEq)]
#[repr(u8)]
enum Status {
#[facet(rename = "on")]
Active,
#[facet(rename = "off")]
Inactive,
}
# let xml_str = "<on/>";
# let status: Status = facet_xml::from_str(xml_str).unwrap();
# assert_eq!(status, Status::Active);
Attributes like #[facet(tag = "type")] or #[facet(tag = "t", content = "c")] are
ignored for XML. They exist for JSON compatibility but don't affect XML serialization:
# use facet::Facet;
#[derive(Facet, Debug, PartialEq)]
#[repr(u8)]
#[facet(tag = "type")] // ignored for XML!
enum Shape {
Circle { radius: f64 },
}
// Still uses element name as discriminator
# let xml_str = "<circle><radius>3.0</radius></circle>";
# let shape: Shape = facet_xml::from_str(xml_str).unwrap();
# assert_eq!(shape, Shape::Circle { radius: 3.0 });
Untagged enums (#[facet(untagged)]) use the enum's own name as the element, not a variant name.
The content determines which variant is selected:
# use facet::Facet;
#[derive(Facet, Debug, PartialEq)]
#[repr(u8)]
#[facet(untagged, rename = "point")]
enum Point {
Coords { x: i32, y: i32 },
}
# let xml_str = "<point><x>10</x><y>20</y></point>";
# let point: Point = facet_xml::from_str(xml_str).unwrap();
# assert_eq!(point, Point::Coords { x: 10, y: 20 });
Thanks to all individual sponsors:
...along with corporate sponsors:
...without whom this work could not exist.
The facet logo was drawn by Misiasart.
Licensed under either of:
at your option.