Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Helper library of useful de/serialize_with functions #553

Closed
dtolnay opened this issue Sep 23, 2016 · 32 comments
Closed

Helper library of useful de/serialize_with functions #553

dtolnay opened this issue Sep 23, 2016 · 32 comments

Comments

@dtolnay
Copy link
Member

dtolnay commented Sep 23, 2016

For example the ones from #550 and serde-rs/serde-rs.github.io#22. These don't need to be in serde itself but if there are ones that people ask for over and over, we can stick those in a helper crate and provide a better out-of-the-box experience compared to "implement your own serialize_with" or "paste this code into your project."

@dtolnay
Copy link
Member Author

dtolnay commented Sep 28, 2016

Another one: include struct name when serializing struct #554.

@dtolnay
Copy link
Member Author

dtolnay commented Oct 13, 2016

Another deserialize_with: a generic comma-separated list of FromStr values #581.

@dtolnay
Copy link
Member Author

dtolnay commented Dec 31, 2016

#661 - as_base64 for AsRef<[u8]> and from_base64 for From<&[u8]>

@oli-obk
Copy link
Member

oli-obk commented Apr 26, 2017

Add a way for deserializing numbers from strings containing numbers (which is a feature that serde 0.9 had implicitly)

@albel727
Copy link

I suggest providing helper functions that would work for anything implementing TryFrom or FromStr traits.

@dtolnay
Copy link
Member Author

dtolnay commented May 7, 2017

Deserializing map and set types while treating duplicate entries as error, and treating duplicate entries as first-one-wins (rather than the current default of last-one-wins). #916

@alexreg
Copy link

alexreg commented May 7, 2017

Deserializing map and set types while treating duplicate entries as error, and treating duplicate entries as first-one-wins (rather than the current default of last-one-wins). #916

How can you treat duplicate entries as both errors and first-one-wins? 🤔

@dtolnay
Copy link
Member Author

dtolnay commented May 7, 2017

Two functions.

@clarfonthey
Copy link
Contributor

clarfonthey commented May 7, 2017

It'd be nice if we could have the same level of configuration available for structs, where instead of erroring on duplicates, we could customise them as first-one-wins or last-one-wins.

Perhaps it might be nice to offer merging them together as well if they're of a map-like or list-like type.

@dtolnay
Copy link
Member Author

dtolnay commented May 7, 2017

Have you ever wanted that in a project? Can you explain the use case in more detail?

@dtolnay
Copy link
Member Author

dtolnay commented Jun 1, 2017

Serialize using Display and deserialize using FromStr, as in serde-rs/json#329.

@spease
Copy link

spease commented Sep 2, 2017

I'm not sure if it would make sense to use Display for serialization, since Display is "for user-facing output" and so may drop data or not be interchangeable with FromStr.

std::string::ToString might be more appropriate for serialization, but I don't know if this is how it's intended to be used either.

@dtolnay
Copy link
Member Author

dtolnay commented Sep 22, 2017

#994 -- represent a field as nested JSON string.

{
    "sms":"{\"Source\":4477665544,\"Destination\":1231231}",
    "uuid":"69e123f4-4ced-4f8f-9853-df20ebc3937b"
}

@dtolnay
Copy link
Member Author

dtolnay commented Nov 5, 2017

#1042 -- a helper for the "double option" pattern for distinguishing null vs absent value.

@oli-obk
Copy link
Member

oli-obk commented Feb 6, 2018

Stringification of a field (type is String, deserializes from numbers and strings, or even more?).

Example implementation: https://play.rust-lang.org/?gist=f0e00482fa5bb52c7c9e3db213d271db&version=stable

@oli-obk
Copy link
Member

oli-obk commented Feb 22, 2018

Note that there now is https://github.com/vityafx/serde-aux which has a few of these functions.

@alexreg
Copy link

alexreg commented Feb 22, 2018

Oh, I was going to start a library of my own along these lines, but it seems someone beat me to it. Maybe we can build on that...

@spease
Copy link

spease commented Feb 26, 2018

Yeah, I was thinking of doing one myself if I got around to it, but I'd rather see there be one library. It's a pain to remember too many library names (and versions if you aren't using cargo-edit)

@alexreg
Copy link

alexreg commented Feb 26, 2018

@spease Probably it will officially come under serde control/ownership once it matures, but we could just submit PRs to this serde-aux project for now. (serde-helpers or serde-util is a better name in my mind.)

@spease
Copy link

spease commented Feb 27, 2018

Here's another misc function. The use case for this is to get enum variant names where I needed to do #[serde(rename_all)] to match an API's case, and serialize them with a pipe (|) in between them.

use serde::ser::{self, Serialize, Serializer, SerializeStructVariant, SerializeTupleVariant, Impossible};
use std;

// Many thanks to dtolnay
pub fn variant_name<T: Serialize>(t: &T) -> &'static str {
    #[derive(Debug)]
    struct NotEnum;
    type Result<T> = std::result::Result<T, NotEnum>;
    impl std::error::Error for NotEnum {
        fn description(&self) -> &str { "not struct" }
    }
    impl std::fmt::Display for NotEnum {
        fn fmt(&self, _f: &mut std::fmt::Formatter) -> std::fmt::Result { unimplemented!() }
    }
    impl ser::Error for NotEnum {
        fn custom<T: std::fmt::Display>(_msg: T) -> Self { NotEnum }
    }

    struct VariantName;
    impl Serializer for VariantName {
        type Ok = &'static str;
        type Error = NotEnum;
        type SerializeSeq = Impossible<Self::Ok, Self::Error>;
        type SerializeTuple = Impossible<Self::Ok, Self::Error>;
        type SerializeTupleStruct = Impossible<Self::Ok, Self::Error>;
        type SerializeTupleVariant = Enum;
        type SerializeMap = Impossible<Self::Ok, Self::Error>;
        type SerializeStruct = Impossible<Self::Ok, Self::Error>;
        type SerializeStructVariant = Enum;
        fn serialize_bool(self, _v: bool) -> Result<Self::Ok> { Err(NotEnum) }
        fn serialize_i8(self, _v: i8) -> Result<Self::Ok> { Err(NotEnum) }
        fn serialize_i16(self, _v: i16) -> Result<Self::Ok> { Err(NotEnum) }
        fn serialize_i32(self, _v: i32) -> Result<Self::Ok> { Err(NotEnum) }
        fn serialize_i64(self, _v: i64) -> Result<Self::Ok> { Err(NotEnum) }
        fn serialize_u8(self, _v: u8) -> Result<Self::Ok> { Err(NotEnum) }
        fn serialize_u16(self, _v: u16) -> Result<Self::Ok> { Err(NotEnum) }
        fn serialize_u32(self, _v: u32) -> Result<Self::Ok> { Err(NotEnum) }
        fn serialize_u64(self, _v: u64) -> Result<Self::Ok> { Err(NotEnum) }
        fn serialize_f32(self, _v: f32) -> Result<Self::Ok> { Err(NotEnum) }
        fn serialize_f64(self, _v: f64) -> Result<Self::Ok> { Err(NotEnum) }
        fn serialize_char(self, _v: char) -> Result<Self::Ok> { Err(NotEnum) }
        fn serialize_str(self, _v: &str) -> Result<Self::Ok> { Err(NotEnum) }
        fn serialize_bytes(self, _v: &[u8]) -> Result<Self::Ok> { Err(NotEnum) }
        fn serialize_none(self) -> Result<Self::Ok> { Err(NotEnum) }
        fn serialize_some<T: ?Sized + Serialize>(self, _value: &T) -> Result<Self::Ok> { Err(NotEnum) }
        fn serialize_unit(self) -> Result<Self::Ok> { Err(NotEnum) }
        fn serialize_unit_struct(self, _name: &'static str) -> Result<Self::Ok> { Err(NotEnum) }
        fn serialize_unit_variant(self, _name: &'static str, _variant_index: u32, variant: &'static str) -> Result<Self::Ok> { Ok(variant) }
        fn serialize_newtype_struct<T: ?Sized + Serialize>(self, _name: &'static str, _value: &T) -> Result<Self::Ok> { Err(NotEnum) }
        fn serialize_newtype_variant<T: ?Sized + Serialize>(self, _name: &'static str, _variant_index: u32, variant: &'static str, _value: &T) -> Result<Self::Ok> { Ok(variant) }
        fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq> { Err(NotEnum) }
        fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple> { Err(NotEnum) }
        fn serialize_tuple_struct(self, _name: &'static str, _len: usize) -> Result<Self::SerializeTupleStruct> { Err(NotEnum) }
        fn serialize_tuple_variant(self, _name: &'static str, _variant_index: u32, variant: &'static str, _len: usize) -> Result<Self::SerializeTupleVariant> { Ok(Enum(variant)) }
        fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> { Err(NotEnum) }
        fn serialize_struct(self, _name: &'static str, _len: usize) -> Result<Self::SerializeStruct> { Err(NotEnum) }
        fn serialize_struct_variant(self, _name: &'static str, _variant_index: u32, variant: &'static str, _len: usize) -> Result<Self::SerializeStructVariant> { Ok(Enum(variant)) }
    }

    struct Enum(&'static str);
    impl SerializeStructVariant for Enum {
        type Ok = &'static str;
        type Error = NotEnum;
        fn serialize_field<T: ?Sized + Serialize>(&mut self, _key: &'static str, _value: &T) -> Result<()> { Ok(()) }
        fn end(self) -> Result<Self::Ok> {
            Ok(self.0)
        }
    }
    impl SerializeTupleVariant for Enum {
        type Ok = &'static str;
        type Error = NotEnum;
        fn serialize_field<T: ?Sized + Serialize>(&mut self, _value: &T) -> Result<()> { Ok(()) }
        fn end(self) -> Result<Self::Ok> {
            Ok(self.0)
        }
    }

    t.serialize(VariantName).unwrap()
}

@clarfonthey
Copy link
Contributor

One thing that I'd suggest now that people are actually providing crates for this is to put the functions in a workspace similar to that for the unic crate: while one master serde-aux crate contains everything, smaller serde-aux-* crates contain self-contained bits of functionality.

Ideally, each sub-crate would be a piece that someone would use in one use case, so that people don't just import one giant crate from which most of the stuff in there is unused.

@alexreg
Copy link

alexreg commented Feb 28, 2018

I'm not sure if there's any obvious logical grouping though...

@spease
Copy link

spease commented Feb 28, 2018

Yeah, and it risks creating a fragmented ecosystem. It already feels like I end up adding the same ~10 crates every time I start a new rust crate. I don't want to have to look up which aux crate something belongs to, or have to search multiple crates for a helper function.

@alexreg
Copy link

alexreg commented Feb 28, 2018

@spease Right. Let's stick to a serde-aux / serde-util crate, for these reasons, I think.

@spease
Copy link

spease commented Apr 27, 2018

Came here to note that I find myself dealing with #908 for multiple classes, and that should probably go in serde-aux. Looks like it's already been noted.

jonasbb added a commit to jonasbb/serde_with that referenced this issue Jul 10, 2018
…maps/sets

Inspired by a comment on serde's helper library issue:
serde-rs/serde#553 (comment)

This behaviour can be used to detect error in the serialized data.
@spease
Copy link

spease commented Oct 29, 2018

Ran into multiple cases of #908 today. How does something like this seems as a solution?

#[proc_macro_derive(DeserializeFromStr)]
pub fn deserialize_from_str(input: TokenStream) -> TokenStream {
    let ast: DeriveInput = syn::parse(input).unwrap();
    let name = ast.ident;
    let gen = quote! {
        impl<'de> Deserialize<'de> for #name {
            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
            where D: Deserializer<'de>
            {
                let s = String::deserialize(deserializer)?;
                FromStr::from_str(&s).map_err(de::Error::custom)
            }
        }
    };
    gen.into()
}

@dtolnay
Copy link
Member Author

dtolnay commented Jan 6, 2019

I am ready to close this issue because external Serde helper libraries now exist! Please continue to file use cases against those libraries, or make your own helper libraries as you see fit.

@dtolnay dtolnay closed this as completed Jan 6, 2019
@alexreg
Copy link

alexreg commented Jan 11, 2019

@dtolnay Do you know if all the above functions are in the serde-aux library? It would be nice to have an official helper library for serde, or at least some amount of official involvement...

@jonasbb
Copy link
Contributor

jonasbb commented Jan 11, 2019

I want to advertise another helper crate, which was not mentioned in the thread: serde_with

With regards to the helper functions from this issue, it is more complete than serde-aux. Some things, like the base64 helpers are not included, because there already exist other crates for that on crates.io.

Neither serde_with nor serde-aux is a superset of the other right now. Part of the reason is that serde-aux is only MIT licensed, while serde_with has a dual Apache2/MIT license, which prohibits code transfer from serde-aux to serde_with.

I am the maintainer of serde_with.
@alexreg

@alexreg
Copy link

alexreg commented Jan 11, 2019

@jonasbb Sounds good. What do you think about trying to combine the two into some sort of serde-utils crate (or keeping the same name even), run by you and maybe with collaborator privileges for Serde Developers? I'd be willing to contribute and help integrate some serde-aux stuff too.

@jonasbb
Copy link
Contributor

jonasbb commented Jan 12, 2019

I am open to extending the crate or changing/migrating the helpers to fit with other crates. This thread is the wrong place to discuss this issue though. You are welcome to leave a comment on the serde_with repository, where we can discuss this in detail. There already is an issue for collaboration with serde-aux.

@alexreg
Copy link

alexreg commented Jan 12, 2019

@jonasbb Fair enough. At least this makes @dtolnay aware of the above though, so he can decide if he wants to pull these efforts closer to official serde, or leave them as they are.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Development

No branches or pull requests

7 participants