facet-xml
facet-xml
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;
# use facet_xml as xml;
# let xml_str = "<banana><taste>sweet</taste></banana>";
# let b: Banana = from_str.unwrap;
# assert_eq!;
Use rename to override:
# use Facet;
# use facet_xml as xml;
# let xml_str = "<Banana><taste>sweet</taste></Banana>";
# let b: Banana = from_str.unwrap;
# assert_eq!;
Child Elements
By default, fields are matched against child elements with the same name (in lowerCamelCase).
Ella
42
# use Facet;
# use facet_xml as xml;
# let xml_str = "<person><name>Ella</name><age>42</age></person>";
# let person: Person = from_str.unwrap;
# assert_eq!;
# assert_eq!;
Attributes
Use xml::attribute to capture XML attributes:
Home
# use Facet;
# use facet_xml as xml;
# let xml_str = r#"<link href="/home">Home</link>"#;
# let link: Link = from_str.unwrap;
# assert_eq!;
# assert_eq!;
Text
Use xml::text to capture text content:
Ella
# use Facet;
# use facet_xml as xml;
# let xml_str = "<name>Ella</name>";
# let name: Name = from_str.unwrap;
# assert_eq!;
Lists
For list types (Vec, etc.), facet-xml collects items. By default, items are child elements with the singularized field name (via facet-singularize).
Default: child elements with singularized name
Song A
Song B
# use Facet;
# use facet_xml as xml;
# let xml_str = "<playlist><track>Song A</track><track>Song B</track></playlist>";
# let playlist: Playlist = from_str.unwrap;
# assert_eq!;
Override element name with rename
# use Facet;
# use facet_xml as xml;
# let xml_str = "<playlist><song>Song A</song><song>Song B</song></playlist>";
# let playlist: Playlist = from_str.unwrap;
# assert_eq!;
Explicit xml::elements (same as default)
# use Facet;
# use facet_xml as xml;
# let xml_str = "<playlist><track>Song A</track><track>Song B</track></playlist>";
# let playlist: Playlist = from_str.unwrap;
# assert_eq!;
Lists of structs with rename
When collecting struct items, use rename to specify the element name. The rename overrides
the default singularized field name:
# use Facet;
# use facet_xml as xml;
# let xml_str = r#"<team><individual name="Alice"/><individual name="Bob"/></team>"#;
# let team: Team = from_str.unwrap;
# assert_eq!;
# assert_eq!;
Collect text nodes with xml::text
Hello world!
# use Facet;
# use facet_xml as xml;
# let xml_str = "<message>Hello world!</message>";
# let msg: Message = from_str.unwrap;
# assert_eq!;
Collect attributes with xml::attribute
# use Facet;
# use facet_xml as xml;
# let xml_str = r#"<element foo="1" bar="2" baz="3"/>"#;
# let elem: Element = from_str.unwrap;
# assert_eq!;
Flattened Lists (Heterogeneous Children)
When you have a Vec<SomeEnum> and want each enum variant to appear directly as a child element
(without a wrapper), use #[facet(flatten)]:
# use Facet;
# use facet_xml as xml;
# let xml_str = r#"<canvas><circle r="5"/><rect width="10" height="20"/><path d="M0 0 L10 10"/></canvas>"#;
# let canvas: Canvas = from_str.unwrap;
# assert_eq!;
Without #[facet(flatten)], the field would expect wrapper elements:
<!-- Without flatten: expects <child> wrappers -->
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
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.
42
hello
true
# use Facet;
# use facet_xml as xml;
# let xml_str = "<record><value>42</value><value>hello</value><value>true</value></record>";
# let record: Record = from_str.unwrap;
# assert_eq!;
Without rename, the field name is used directly (no singularization for tuples since tuple fields typically have singular names):
# use Facet;
# use facet_xml as xml;
# let xml_str = "<record><data>42</data><data>hello</data><data>true</data></record>";
# let record: Record = from_str.unwrap;
# assert_eq!;
Enums
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
Unit variants become empty elements:
# use Facet;
// "Active" becomes <active> (lowerCamelCase)
# let xml_str = "<active/>";
# let status: Status = from_str.unwrap;
# assert_eq!;
Newtype variants
Newtype variants (single unnamed field) wrap their content:
# use Facet;
// <text>hello</text> deserializes to Value::Text("hello")
# let xml_str = "<text>hello</text>";
# let value: Value = from_str.unwrap;
# assert_eq!;
Struct variants
Struct variants have child elements for their fields:
# use Facet;
// <circle><radius>5.0</radius></circle>
# let xml_str = "<circle><radius>5.0</radius></circle>";
# let shape: Shape = from_str.unwrap;
# assert_eq!;
Variant renaming
Use #[facet(rename = "...")] on variants to override the element name:
# use Facet;
# let xml_str = "<on/>";
# let status: Status = from_str.unwrap;
# assert_eq!;
Internally/adjacently tagged enums
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;
// ignored for XML!
// Still uses element name as discriminator
# let xml_str = "<circle><radius>3.0</radius></circle>";
# let shape: Shape = from_str.unwrap;
# assert_eq!;
Untagged enums
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;
# let xml_str = "<point><x>10</x><y>20</y></point>";
# let point: Point = from_str.unwrap;
# assert_eq!;
Sponsors
Thanks to all individual sponsors:
...along with corporate sponsors:
...without whom this work could not exist.
Special thanks
The facet logo was drawn by Misiasart.
License
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.