iddm / serde-aux

An auxiliary serde library providing helpful functions for serialisation and deserialisation for containers, struct fields and others.
MIT License
152 stars 26 forks source link

Feature Request: Get the field names of a struct #19

Closed ta32 closed 3 years ago

ta32 commented 3 years ago

Hi

Would a function implemented in this issue, be useful in this crate? https://github.com/serde-rs/serde/issues/1110

fn main() {
    // prints ["error", "description"]
    println!("{:?}", struct_fields::<AuthError>());
}

A function like this would eliminate a lot of boiler plate code in my app. I copied and pasted the implementation into my application and it works but I think it would be useful as apart of this library. The original issue was closed.

Note: link has implementation for enum and struct

iddm commented 3 years ago

Do you think this crate might be of use? https://github.com/vityafx/introspection

ta32 commented 3 years ago

I should have mentioned that the function in serde-rs/serde#1110, will get the field names as they are serialized - including the effect of macros. Eg

#[macro_use] extern crate serde;
#[macro_use] extern crate serde_derive;

use serde::de::{self, Deserialize, Deserializer, Visitor};

#[derive(Deserialize)]
#[serde(rename_all = "lowercase")]
enum Setting { On, Off }

fn enum_variants<'de, T>() -> &'static [&'static str]
    where T: Deserialize<'de>
{
    struct EnumVariantsDeserializer<'a> {
        variants: &'a mut Option<&'static [&'static str]>,
    }

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

        fn deserialize_any<V>(self, _visitor: V) -> Result<V::Value, Self::Error>
            where V: Visitor<'de>
        {
            Err(de::Error::custom("I'm just here for the variants"))
        }

        fn deserialize_enum<V>(
            self,
            _name: &'static str,
            variants: &'static [&'static str],
            visitor: V
        ) -> Result<V::Value, Self::Error>
            where V: Visitor<'de>
        {
            *self.variants = Some(variants);
            self.deserialize_any(visitor)
        }

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

    let mut variants = None;
    let _ = T::deserialize(EnumVariantsDeserializer { variants: &mut variants });
    variants.unwrap()
}

fn main() {
    // prints ["on", "off"]
    println!("{:?}", enum_variants::<Setting>());
}

Where as introspection gets the field names generically. Eg because the Setting enum has "#[serde(rename_all = "lowercase")]" the field names will be different.

iddm commented 3 years ago

I am still struggling with understanding the use-case, but that is something you could answer, I guess. If you think it helps, then yes, let's go add it. Would you like to do this or would you like me to do that myself?

ta32 commented 3 years ago

I was basically querying an API and de-serializing the results into a vector of structs.

The API needs a query selecting all the field names of the struct, matching how they would be serialized.

For some of the fields I used directives like #[serde(rename = "FIELDNAME")] As the struct member would be field_name.

I created a method for the struct which calls: let fields = struct_fields::<Self>().to_vec();

This returns a list of field names as they would be serialized/de-serialized by serde.

I can have ago at creating a PR if you want. I guess I would need to create some tests and some usage comments?

iddm commented 3 years ago

Thanks for clarifying the intent and the use case. Yes please, go ahead if you have time. And yes, please, try to provide such doc-comments with tests and usage.

ta32 commented 3 years ago

@vityafx I checked out your repo and created a branch where I implemented the changes, but I cant push my branch to remote.

This is my first time contributing to open source so I am not sure about the correct procedure. Do I need to fork the project and merge the code there?

iddm commented 3 years ago

@ta32 Yes, this is how it works. First, you fork a repository so that GitHub knows the connection between a fork and the upstream repository. Then you work with the fork by cloning it and making changes there, and then you open a pull request proposing your changes to be merged to the upstream.

iddm commented 3 years ago

Done in https://github.com/vityafx/serde-aux/pull/20

iddm commented 3 years ago

I have just published 2.3.0 with your changes.