Home > Net >  Deserialize an XML field directly into an enum unit variant of the same name using quick_xml, serde
Deserialize an XML field directly into an enum unit variant of the same name using quick_xml, serde

Time:11-04

Context

While trying to find a more rustic way of handling casting to rust types while reading a binary file by using its XML description it was suggested to use an enum with unit types instead of deserializing a string or &str, to get more guarantees from the compiler and avoid lifetimes; however, getting the XML to deserialize directly into the enum unit variants is causing some issues.

The XML File

<Records>
    <Record>
        <name>Single</name>
        <number>1</number>
        <location>0</location>
        <data_type>IEEE754LSBSingle</data_type>
        <length>4</length>
    </Record>
    <Record>
        <name>Double</name>
        <number>2</number>
        <location>4</location>
        <data_type>IEEE754LSBDouble</data_type>
        <length>8</length>
    </Record>
    <Record>
        <name>SingleArr</name>
        <number>3</number>
        <location>11</location>
        <data_type>IEEE754LSBSingleArr</data_type>
        <length>8</length>
    </Record>
    <Record>
        <name>DoubleArr</name>
        <number>4</number>
        <location>18</location>
        <data_type>IEEE754LSBDoubleArr</data_type>
        <length>16</length>
    </Record>
</Records>

The Code

use binary_type_cast::PrettyPrint;
use quick_xml::{de::from_reader, DeError};
use serde::{Deserialize};
use std::{fs::File, io::BufReader};

#[derive(Clone, Copy, Debug, Deserialize)]
//#[serde(tag="data_type")]
//#[serde(untagged)]
pub enum DataTypes {
    // 4 bytes
    IEEE754LSBSingle,
    // 8 bytes
    IEEE754LSBDouble,
    // [4 bytes, 4 bytes]
    IEEE754LSBSingleArr,
    // [8 bytes, 8 bytes]
    IEEE754LSBDoubleArr,
    // Include ASCIIString just to make sure records are still gathered even if none of them use this
    ASCIIString,
}

#[derive(Debug, Deserialize)]
pub struct Records {
    #[serde(rename="Record")]
    pub records: Vec<Record>,
}

#[derive(Debug, Deserialize)]
pub struct Record {
    pub name: String,
    pub number: u32,
    pub location: u32,
    //#[serde(flatten)]
    pub data_type: DataTypes,
    pub length: u32
}

pub fn get_xml_record(file: &File) -> Result<Records, DeError> {
    let reader = BufReader::new(file);    
    let records: Records = from_reader(reader).unwrap();
    Ok(records)
}

fn main() {
    let description = "./data/desc.xml";
    if let Ok(file) = File::open(description) {
        if let Ok(records) = get_xml_record(&file) {
            println!("{:#?}",records);

        }
    }

}

I attempted to implement various combinations of the commented out serde attributes seen in the presented code. It seems like creating a custom deserializer that would match the string from the XML and then output the associated DataTypes variant would essentially replicate the behavior of the original post in the other issue where the strings were matched and converted manually.

CodePudding user response:

This appears to be a bug in quick-xml

I got it to work without much fuss using serde-xml-rs instead:

use serde_xml_rs::from_str;
use serde::Deserialize;

#[derive(Clone, Copy, Debug, Deserialize)]
pub enum DataTypes {
    // 4 bytes
    IEEE754LSBSingle,
    // 8 bytes
    IEEE754LSBDouble,
    // [4 bytes, 4 bytes]
    IEEE754LSBSingleArr,
    // [8 bytes, 8 bytes]
    IEEE754LSBDoubleArr,
    // Include ASCIIString just to make sure records are still gathered even if none of them use this
    ASCIIString,
}

#[derive(Debug, Deserialize)]
pub struct Records {
    #[serde(rename = "$value")]
    pub records: Vec<Record>,
}

#[derive(Debug, Deserialize)]
pub struct Record {
    pub name: String,
    pub number: u32,
    pub location: u32,
    pub data_type: DataTypes,
    pub length: u32
}

fn main() {
    let source = r##"
<Records>
    <Record>
        <name>Single</name>
        <number>1</number>
        <location>0</location>
        <data_type>IEEE754LSBSingle</data_type>
        <length>4</length>
    </Record>
    <Record>
        <name>Double</name>
        <number>2</number>
        <location>4</location>
        <data_type>IEEE754LSBDouble</data_type>
        <length>8</length>
    </Record>
    <Record>
        <name>SingleArr</name>
        <number>3</number>
        <location>11</location>
        <data_type>IEEE754LSBSingleArr</data_type>
        <length>8</length>
    </Record>
    <Record>
        <name>DoubleArr</name>
        <number>4</number>
        <location>18</location>
        <data_type>IEEE754LSBDoubleArr</data_type>
        <length>16</length>
    </Record>
</Records>
    "##;

    let records: Records = from_str(source).unwrap();
    dbg!(records);
}

Output:

[src\main.rs:71] records = Records {
    records: [
        Record {
            name: "Single",
            number: 1,
            location: 0,
            data_type: IEEE754LSBSingle,
            length: 4,
        },
        Record {
            name: "Double",
            number: 2,
            location: 4,
            data_type: IEEE754LSBDouble,
            length: 8,
        },
        Record {
            name: "SingleArr",
            number: 3,
            location: 11,
            data_type: IEEE754LSBSingleArr,
            length: 8,
        },
        Record {
            name: "DoubleArr",
            number: 4,
            location: 18,
            data_type: IEEE754LSBDoubleArr,
            length: 16,
        },
    ],
}
  • Related