#how can I (re)use a custom serde deserializer?

62 messages · Page 1 of 1 (latest)

regal crystal
#

I am using a type (let's say Foo) that already implements Deserialize in its own crate, but it's not the one I want.

I can easily use #[serde(deserliaze_with = "custom_de"] on fields:

struct Bar {
     #[serde(deserliaze_with = "deserialize_hashmap_foo_value"]
     map: HashMap<String, Foo>
}

pub fn deserialize_hashmap_foo_value<'de, D, T>(d: D) -> Result<HashMap<T, Foo>, D::Error>
where
    D: Deserializer<'de>,
    T: Deserialize<'de> + std::hash::Hash + std::cmp::Eq,
{
    let map: HashMap<T, String> = Deserialize::deserialize(d)?;
    Ok(map
        .into_iter()
        .map(|(k, v)| (k, Foo::my_custom_deser(&v).unwrap()))
        .collect())
}

And I am wondering whether I could re-use that function when I need to deserialize a struct myself, something like:

let response = &reqwest::get(url).bytes().await?
let result: HashMap<String, Foo> = serde_json::form_slice_with_deser(response, deserialize_hashmap_value_foo);

I've tried to define a newtype U256ValuedHashMap<T> and implementing from, into on it, but that creates quite a bit of mental over-head, and doesn't scale. Maybe a macro to define that type inline could work, idk...

Bonus

I'm also surprised how I can't just feed serde a custom deserialier for a type but I need to specify a custom deserializer for any wrapper value I want (HashMap, BTree, HashSet, Vec,...), that's seems like a very broad use case, am I missing some API?

#

I'd love a way to tell serde to just forget about the implemented deserialization for U256 and use mine instead, but the only way I found that is via new-types, and that gets me into wrapping/un-wrapping hell, because the base Foo type is the one that is compatible with the rest of the APIs

brave ferry
#

The simplest way is probably to take it out of the wrapper after deserializing:

impl Bar {
  fn into_inner(self) -> HashMap<String, Foo> {
    self.map
  }
}

fn create_foo_map<R: std::io::Read>(reader: R) -> Foo {
  serde_json::from_reader::<R, Bar>(reader).into_inner()
}
regal crystal
#

oh in my case Bar is a Config struct, much bigger, didn't mean to post it as a wrapper type, just to showcase deserialize_with

#

Added a note below about wrapper types

#

Seems like the namespace would be very polluted if I added a wrapper type for any of these I need?

brave ferry
#

They don't need to be public.

cyan rampart
regal crystal
#

I wish it was just possible to implement a trait for a type of an outside crate

brave ferry
#

Is the problem that there's a bunch of U256 in different fields inside Config, and you want to change them all at the same time?

cyan rampart
regal crystal
#

however as deserialize_with

#

it can't be used inline right?

#

as in this example

#
let response = &reqwest::get(url).bytes().await?
let result: HashMap<String, Foo> = serde_json::from_slice(response);```
#

something like:

let result: HashMap<String, #[serde(deserialize_with = "path")]Foo> = serde_json::from_slice(response)
#

maybe this is allowed?

#
let result: HashMap<
  String, 
  #[serde(deserialize_with = "path")]
  Foo
> = ...
#

doesn't look too bad!

brave ferry
#

That's definitely not going to work

regal crystal
#

you can't annotate expressions right

#

otherwise also

  #[serde(deserialize_with = "path")]
let result: HashMap<...

would be possible

brave ferry
#

Still, this would be better written

let result = serde_json::from_slice::<Bar>(response).map;
```So it's not equivalent to whatever problem you're having.
regal crystal
#

but that wouldn't scale

#

serde_as also doesn't scale in my case, as I would need to have that and also keep the deserialize_hashmap function around in order to work with inline cases like the reqwest above

#

thinking of a hybrid solution

#

like after I use serde_as, how would I get rid of my function

brave ferry
#

What's your actual struct?

regal crystal
#

I'd still need to write a case-specific deserializer for any wrapper

regal crystal
brave ferry
#

Then just wrap the hashmap?

cyan rampart
#

If you need it inline, you do have some options

let result: HashMap<String, i32> = serde_json::from_value::<
    serde_with::de::DeserializeAsWrap<_, HashMap<Same, DisplayFromStr>>,
>(serde_json::json!({
    "foobar": "123"
}))
.unwrap()
.into_inner();
let result: HashMap<String, i32> = As::<HashMap<Same, DisplayFromStr>>::deserialize(
    serde_json::json!({
        "foobar": "123"
    })
    .into_deserializer(),
)
.unwrap();
#

It is less pretty, since you have to specify the result type once, but also the logic how to convert

regal crystal
#

so let me see what's going on

#

As is from serde_as

#

?

cyan rampart
regal crystal
#

wait where is into_deserializer() coming from

#

that might be all i need here

#

let me check it out

cyan rampart
regal crystal
#

less steps bc I already have the deserialize_hashmap function,

#

i only need a deserializer and apparently serde comes with an into_deserializer I didn't know of

#

I think I'll write both

#

starting from 2

#

than pick up which one feels the best

#

probably, if I import serde_with, first is best, but then I'm importing an additional dependency...

#

also first scales better as with additional wrappers I wouldn't have to write additional deserializer functions

regal crystal
cyan rampart
#

You should write a custom deserialize for Foo and not use deserialize_hashmap. That way you can re-use it for Vec<Foo> or Option<Foo> if you need.

regal crystal
#

I agree, that is only possible via serde_with atm, as the only other way would be overriding Deserialize which rust doesn't allow, correct?

cyan rampart
#

but you can use the my_custom_deser function. The second link should give enough detail how to get started with implementing.

regal crystal
#

lgtm!

#

thanks!

regal crystal
#

maybe .into() at the end there?

regal crystal
#

hey looks like DeserializeAsWrapped works for that