get structure header names with serde Serialize preserving order

Viewed 69

Rust data structure.

This is my implementation to get headers preserving order:

use serde::{Deserialize, Serialize};
use csv::{Reader, Writer, StringRecord};

#[derive(Default, Debug, Deserialize, Serialize)]
pub struct Colunas {
    #[serde(rename = "Arquivo Teste")]
    arquivo: String,
    #[serde(rename = "CNPJ dos Estabelecimentos")]
    cnpj: Option<String>,
    #[serde(rename = "Ano do Período de Apuração")]
    ano: Option<usize>,
    #[serde(rename = "Mês")]
    m: u32,
}

impl Colunas {
    fn get_headers(self) -> StringRecord {
        // https://docs.rs/csv/1.0.0/csv/struct.Writer.html
        let mut wtr = Writer::from_writer(vec![]);
        wtr.serialize( Colunas::default() ).ok();
        
        let colunas = String::from_utf8(wtr.into_inner().ok().unwrap()).ok().unwrap();
        
        // split-a-string-in-rust
        let mut lines = colunas.lines();
        let first_line = lines.next().unwrap();
        //println!("first_line: {:#?}", &first_line);
        
        let vec: Vec<&str> = first_line.split(',').collect();
        //println!("vec: {:#?}", vec);
        
        //https://docs.rs/csv/1.0.0-beta.2/csv/struct.StringRecord.html
        // https://docs.rs/csv/1.0.0/csv/struct.Reader.html
        let mut rdr = Reader::from_reader("".as_bytes());
        rdr.set_headers(StringRecord::from(vec));
        
        let headers = rdr.headers().ok().unwrap();
        //println!("headers: {:#?}", headers);
        
        headers.clone()
    }
}

fn main() {
    let headers = Colunas::default().get_headers();
    println!("headers: {:#?}", headers);
}

Is there any other more elegant and concise way?

Rust playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=24cd9eeed8f5fbfa2abf0eff467a03a2

The output is:

headers: StringRecord(["Arquivo Teste", "CNPJ dos Estabelecimentos", "Ano do Período de Apuração", "Mês"])

1 Answers

I don't know of any concise and elegant way. But I can give you concise via a trip through serde_json::Value

serde_json::to_value(&Colunas::default())
    .unwrap()
    .as_object()
    .unwrap()
    .keys()
    .collect()

(Enabling the preserve_order feature on serde_json should preserve the order of the fields in the struct.)

As for elegant, one part I consider inelegant is that you have to make Colunas impl Default for this. This can be avoided with a custom deserializer, which has access to the structure without ever needing a value.

struct FieldTracingDeserializer<'a> {
    fields: &'a mut Vec<&'static str>,
}

impl<'de, 'a> serde::Deserializer<'de> for FieldTracingDeserializer<'a> {
    type Error = serde::de::value::Error;

    fn deserialize_any<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
    where
        V: serde::de::Visitor<'de>,
    {
        panic!("Only works for structs");
    }

    serde::forward_to_deserialize_any! {
        bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
        bytes byte_buf option unit unit_struct newtype_struct seq tuple
        tuple_struct map enum identifier ignored_any
    }

    fn deserialize_struct<V>(
        self,
        _name: &'static str,
        fields: &'static [&'static str],
        _visitor: V,
    ) -> Result<V::Value, Self::Error>
    where
        V: serde::de::Visitor<'de>,
    {
        // Would be cleaner to return fields through a custom error struct.
        // But also more work.
        self.fields.extend_from_slice(fields);
        Err(serde::de::Error::custom("success"))
    }
}

let mut headers = vec![];
Colunas::deserialize(FieldTracingDeserializer {
    fields: &mut headers,
})
.ok();
headers.into_iter().collect()

I don't really think this can be called elegant… There are however crates that implement this exact functionality (e.g. serde-aux):

serde_aux::serde_introspection::serde_introspect::<Colunas>()
Related