-
-
Notifications
You must be signed in to change notification settings - Fork 782
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
Field serialization proxy via newtypes #198
Conversation
Very interesting concept. Maybe something like this could be used to implement the various feature requests like skipping fields. But that's for a future PR. It would certainly stop the current attribute inflation and simplify the design of the current attributes. One potential flaw with your current code might be generics. |
The main motivation here was that I'm constantly making two or three copies of structures:
While the current serde design doesn't really allow for this, I saw a "pre-mapkey-serialize-check-hook" kind of proposal that would certainly make that work; you could then implement
Hm, what about generics? |
Fun fact: For * actually I mean just look at this fine and very readable exemplar that actually works: #[derive(Serialize)]
struct S {
a: u8,
#[serde(serializer = "|b: &u8| self.value.a + *b")]
b: u8,
} Note: while I think this could be potentially useful in certain scenarios, it needs more design and consideration. I'd suggest documenting that only an ident or path is allowed in this field, and anything else is not guaranteed to be compatible with later versions until a design has been finalized. |
Interesting! This is a neat idea. I've long wanted to have Another option would be to use functions instead of types, as in: struct Foo {
#[serde(serializer = "some_ser_fn")]
#[serde(deserializer = "some_de_fn")]
x: u8,
} And we'd codegen calls out to functions that had this interface: fn some_ser_fn<T>(ser: &mut T, value: u8) -> Result<(), T::Error>
where T: Serializer {
...
}
fn some_de_fn<T>(de: &mut T) -> Result<u8, T::Error>
where T: Deserializer {
...
} Would that make thing simpler, or more complicated? That would mean that you wouldn't have to implement I'm not sure if this could implement "skip_if_empty" though. That check needs to happen before we serialize the field name, but the obvious implementation of this we would have already serialized the field name at this point. |
Hard to say. I considered it, but don't remember why I went with this approach. Let's break it down...
This is all very confusing to think about...
Yeah... I saw previous discussion about adjusting the traits to allow this in an issue somewhere, but that's a bit out of scope of this PR. It would be very nice though if possible! This may be one of those examples where using
While I'd like to make this possible via this PR's |
Well... if there's a method on the value that simply returns
This might be a bit too magical, but the default could automatically be taken if the |
@oli-obk: Another option would be when serializing fields, the |
sounds good. So we still have the open question on whether to use newtypes or free functions. I guess free function have less boilerplate while newtypes have a better transition strategy for people already using newtypes in their structs and they also are more concise. Since it's already implemented for newtypes, I don't see why it should be changed without a strong argument for free functions. |
@arcnmx We might possibly avoid COWs and have single "proxy" type, assuming that if we have |
I often implement APIs to different JSON-based HTTP services, and I often have a situation when physical type doesn't match logical one. E.g. number ( This PR would help me very much. |
@target-san: I'd strongly prefer to avoid using unsafe code if possible. I'd hate for serde to introduce potential memory bugs in difficult-to-audit code generated code. |
@target-san what @erickt said. We could create a trait like:
for it and whatever else is needed though. I need to circle 'round and think on the best interface for all of this again soon... Need something solid and nice to use to propose. |
@erickt Surely this looks like a hack. Though, if we know that newtype has the same memory layout as its wrapped type (and this is almost 100%, due to Rust's zero overhead policy), I personally don't see big problem here. First, only newtype (i.e. tuple struct with 1 element) can be used as proxy. Second, no one prohibits us from adding small utility function which will perform transmute under the hood. At least, it's worth trying. @arcnmx Trait might be the solution, but it will hammer down ergonomics. And some proxies might have only one way to do the trick. Though, we can play on the fact that newtype declaration also creates x -> y function which creates newtype instance from nested type's value. So I assume we have basically two ways here
|
This was alternatively implemented in #238. Thanks though! |
This change adds two new attributes:
serializer
anddeserializer
. They can be used to specify a newtype that performs {de,}serialization functionality without actually making it part of your struct type definition.Mainly looking for design and implementation feedback. I did this pretty quickly and haven't tested it much with real-life workloads yet. I may have missed some cases (enum variants? tuples?).
serializer
An expression that resolves to a function or newtype that, when called, produces a value to be serialized. The prototype is roughly
for<T: serde::Serialize> FnOnce(&ValueType) -> T
.deserializer
A name or path to a type that satisfies
T: serde::Deserialize + Into<ValueType>