-
-
Notifications
You must be signed in to change notification settings - Fork 791
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
Using de/serialize_with inside of an Option, Map, Vec #723
Comments
Ideally I would like to find an approach that composes better than keysUsing. #[derive(Deserialize)]
struct S {
#[serde(deserialize_with = "my_key")]
key: K,
#[serde(deserialize_with = "my_value")]
value: V,
#[serde(???)]
opt_map: Option<Map<K, V>>,
} |
{deserialize,serialize}_{keys,values}_with
support to serde-derive
#576 has an approach based on a helper for generating ordinary deserialize_with functions, rather than using a slate of new attributes. |
This could be neat: #[derive(Deserialize)]
struct S {
#[serde(deserialize_with = "my_key")]
key: K,
#[serde(deserialize_with = "my_value")]
value: V,
#[serde(deserialize_with = "my_opt_map")]
opt_map: Option<Map<K, V>>,
}
fn my_map<'de, D>(deserializer: D) -> Result<Map<K, V>, D::Error>
where D: Deserializer<'de>
{
deserialize_map_with!(my_key, my_value)(deserializer)
}
fn my_opt_map<'de, D>(deserializer: D) -> Result<Option<Map<K, V>>, D::Error>
where D: Deserializer<'de>
{
deserialize_option_with!(my_map)(deserializer)
} |
Another possible composable approach: #[derive(Deserialize)]
struct S {
#[serde(deserialize_with = "my_k")]
k: K,
#[serde(deserialize_with = "option!(my_k)")]
opt: Option<K>,
#[serde(deserialize_with = "option!(map!(my_k, my_v))")]
opt_map: Option<Map<K, V>>,
#[serde(deserialize_with = "map!(_, my_v)")]
map: Map<u8, V>,
} |
This needs to support all the wrapper types too: Rc, Arc, Cell, RefCell, Mutex, RwLock. |
Would a syntax like this also want to support custom key/values within a custom map deserializer? Like #[derive(Deserialize)]
struct S {
#[serde(deserialize_with = "my_k")]
k: K,
#[serde(deserialize_with = "option!(my_k)")]
opt: Option<K>,
#[serde(deserialize_with = "my_map")]
map: Map<u8, u8>,
#[serde(deserialize_with = "option!(map!(my_k, my_v))")]
opt_map: Option<Map<K, V>>,
#[serde(deserialize_with = "map!(_, my_v)")]
map: Map<u8, V>,
#[serde(deserialize_with = "map::<my_map>!(my_k, my_v)")]
map: Map<K, V>,
} or would this not be feasible at all with the current Deserializer architecture? |
Yes, probably by means of a trait that is implemented for all types that support |
hm, ok. Would that mean the I mean For a syntax alternative, what would you think of something like "inner" attribute, like this? #[derive(Deserialize)]
struct S {
#[serde(deserialize_with = "my_k")]
k: K,
#[serde(inner(K, deserialize_with = "my_k"))]
opt: Option<K>,
#[serde(deserialize_with = "my_map")]
map: Map<u8, u8>,
#[serde(inner(K, deserialize_with = "my_k"))]
#[serde(inner(V, deserialize_with = "my_v"))]
opt_map: Option<Map<K, V>>,
#[serde(inner(V, deserialize_with = "my_v"))]
map: Map<u8, V>,
#[serde(deserialize_with = "my_map")]
#[serde(inner(K, deserialize_with = "my_k"))]
#[serde(inner(V, deserialize_with = "my_v"))]
map: Map<K, V>,
} If this kind of syntax would be allowed in attributes, and if it relatively matched what we can feasibly make happen, it would also provide an intuitive way to include #914: #[derive(Deserialize)]
struct S {
#[serde(inner(Cow<'a, str>, borrow))]
cow: Vec<Cow<'a, str>>,
} Edit: again, thinking entirely " ideal situation" here, but could we have this attribute support literally all field attributes by having the Deserialize impl create a newtype for each If I understand the situation correctly, having it create a newtype would let any inner attributes apply to the newtypos single field, and the changes in deserialization could be fully conveyed statically with no cost. Thoughts? Or... any obvious contradictions which I completely overlooked which would invalidate this? |
Sorry, I think I was just on the complete wrong track there. My apologies for not researching how all of this actually works and thinking about it before commenting! I think I can agree now that a trait is probably the best way to do this. |
I'm pretty new to Rust and Serde, but since you asked (in e.g. #999 and #1005) for people's thoughts on a design, here's how I would like this to work as a user:
I don't know enough about Rust macros and Serde to say whether this is actually implementable. |
I guess no design has been decided yet? I have a struct that has a fn from_toml_datetime<'de, D>(deserializer: D) -> StdResult<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
toml::value::Datetime::deserialize(deserializer)
.map(|s| Some(s.to_string()))
} was my attempt at The above was my intuition so I would vote for something like that, if it's doable. Is there a way to do that at all currently? |
@Keats that looks like it should work correctly. Is a field marked with Edit: just realized you included the attribute. I would recommend also adding |
I was missing the |
What does anyone think of including non-(de)serialize_with attributes in this issue? I'm currently running into a situation where it would make sense to use a |
Is there any way to workaround it? I have a struct like: struct Snippet {
annotations: Vec<Annotation>
}
#[derive(Deserialize)]
#[serde(remote = "Snippet")]
struct SnippetDef {
#[serde(with = "AnnotationDef")]
annotations: Vec<Annotation>
} and that of course doesn't work. How should I solve it until this is fixed? |
My understanding is that you'd work around it by making a method, roughly: fn deserialize_annotation_vec<'de, D>(deserializer: D) -> Result<Vec<Annotation>, D::Error> {
struct AnnotationVecVisitor;
impl<'de> Visitor<'de> for AnnotationVecVisitor {
type Value = Vec<Annotation>;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "a list of annotations") }
fn visit_seq<A: SeqAccess<'de>>(self, seq: A) -> Result<Vec<Annotation>, A::Error> {
let mut vec = Vec::with_capacity(cmp::min(seq.size_hint().unwrap_or(0), 4096));
while let Some(v) = seq.next_element()? {
// assert type
let v = <AnnotationDef as Into>::into(v);
vec.push(v);
}
Ok(vec)
}
}
deserializer.deserialize_seq(AnnotationDefVisitor)
} Then you'd use Sources:
|
I would implement it as: use serde::{Deserialize, Deserializer};
#[derive(Deserialize)]
#[serde(remote = "Snippet")]
struct SnippetDef {
#[serde(deserialize_with = "vec_annotation")]
annotations: Vec<Annotation>,
}
fn vec_annotation<'de, D>(deserializer: D) -> Result<Vec<Annotation>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Wrapper(#[serde(with = "AnnotationDef")] Annotation);
let v = Vec::deserialize(deserializer)?;
Ok(v.into_iter().map(|Wrapper(a)| a).collect())
} |
Any progress on this? I've tried to use the |
@alexdevteam I'd love to hear in more details what you tried with |
Recently I tried to use @dtolnay's excellent string_or_struct function on a vector. Building on David's code, let's assume we have instead: #[derive(Deserialize, Serialize)]
pub struct ManyBuilds {
some_fields_with_overall_info: i32, // etc
// ideally we'd have #[serde(deserialize_with = "string_or_struct") or other notation
builds: Vec<Build>
}
// see https://serde.rs/string-or-struct.html for Build's definition and traits I tried @dtolnay's also-excellent wrap function from this thread, although I got generic and lifetime errors that I don't yet have the skills to resolve (I'll try again!). I also couldn't find how to do this with My current solution is a newtype: #[derive(Deserialize, Serialize)]
pub struct ManyBuildsInput {
some_fields_with_overall_info: i32, // etc
builds: Vec<BuildWrap>
}
#[derive(Deserialize, Serialize)]
struct BuildWrap(#[serde(deserialize_with = "string_or_struct") Build) Then implement From/TryFrom etc to and from the (by the way - @dtolnay thanks for the crate, serde is amazing!) |
@colmanhumphrey I am happy for any feedback and if you need help using #[serde_as(as = "Vec<PickFirst<(_, DisplayFromStr)>>")]
// ^ deserialize struct
// ^^^^^^^^^^^^^^ or FromStr
builds: Vec<Build> |
Thanks @jonasbb - just created jonasbb/serde_with#333! |
Given Maybe inability to use containers should be documented explicitly, ideally with some special error message that points users to workarounds? |
Just bumped into this as well, surprised it's still not handled. |
Unless I'm missing something, this seems not only possible but fairly ergonomic? For example: pub struct Jwk {
#[serde(
with = "b64::optional_seq_url_safe",
skip_serializing_if = "Option::is_none",
default
)]
pub x5c: Option<Vec<Vec<u8>>>,
}
pub(crate) mod b64 {
pub(crate) mod optional_seq_url_safe {
#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
use serde::{self, ser::SerializeSeq, Deserialize, Deserializer, Serializer};
pub fn serialize<S, V, T>(input: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: AsRef<[V]>,
V: AsRef<[u8]>,
{
if input.is_none() {
return serializer.serialize_none();
}
let input = input.as_ref().unwrap().as_ref();
let mut ser = serializer.serialize_seq(Some(input.len()))?;
for item in input {
ser.serialize_element(&URL_SAFE_NO_PAD.encode(item.as_ref()))?;
}
ser.end()
}
pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: From<Option<Vec<Vec<u8>>>>,
{
let data = Option::<Vec<String>>::deserialize(deserializer)?;
let data = data
.map(|data| {
data.into_iter()
.map(|data| {
URL_SAFE_NO_PAD
.decode(data)
.map_err(serde::de::Error::custom)
})
.collect::<Result<Vec<_>, _>>()
})
.transpose()?;
Ok(data.into())
}
}
} |
The main problem with that is just that you have to write a custom module for every different modification. This issue is asking for a generic way to turn some serialization module that serializes/deserializes Doing it manually works fine, if you know what's needed. But if you're writing a crate for general use, what modules do you provide? I've done this in the past for internal usage too, usually having two modules, It's annoying to write N modules for N different use cases, but I mean sure it works if you're the only user. It'd be much more out of hand if you were trying to write a library, though. For my use case for this, https://github.com/daboross/serde-tuple-vec-map/, I've experimented with another option: just provide a wrapper type in addition to the main module. Have someone do |
Just released a small crate to help work around the issue: https://crates.io/crates/serde_nested_with |
Workaround for <serde-rs/serde#723>.
Workaround for <serde-rs/serde#723>.
* Add helpers to deserialise JSON-LD in a more robust way * Add brief introduction to JSON-LD data model * Fix deserialisation of `Option<_>` fields Workaround for <serde-rs/serde#723>. * Remove `AttributedToListEntry` type * Minor cleanup * Replace `&x[..]` with `x.as_str()` Per review comment at: <#492 (comment)>. Co-Authored-By: Aumetra Weisman <aumetra@cryptolab.net> --------- Co-authored-by: Aumetra Weisman <aumetra@cryptolab.net>
These would be equivalent to Jackson's
@Serialize(keysUsing=...)
etc. Now that we have stateful deserialization, this can be implemented in a pretty straightforward way.I believe we'd want to support map keys and values as well was seq and option values.
The text was updated successfully, but these errors were encountered: