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

Can't figure out how to get Option<js_sys::Date> to work when serializing to JS #62

Closed
ronnybremer opened this issue Feb 1, 2024 · 11 comments

Comments

@ronnybremer
Copy link

I have a struct containing a js_sys::Date field, which works perfectly with the preserve module:

pub struct Params {
    #[serde(
        skip_deserializing,
        default = "js_sys::Date::new_0",
        with = "serde_wasm_bindgen::preserve"
    )]
    pub testedTSAsDateTime: js_sys::Date,
}

However, I need to adopt it to hold an "undefined/null" JS value, so I tried this:

pub struct Params {
    #[serde(
        skip_deserializing
    )]
    pub testedTSAsDateTime: Option<js_sys::Date>,
}

It fails to compile:

100 | #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
| ^^^^^^^^^ the trait wasm_bindgen::JsCast is not implemented for std::option::Option<js_sys::Date>

I tried various approaches to no avail. Could you give me any hint on how to approach this issue?

@RReverser
Copy link
Owner

Yeah not being able to apply serde(with) on containers is a general old issue in Serde design, see serde-rs/serde#723. Unfortunately, I'm not sure if there's something we can do here...

Your best bet for now is probably to change the type of that field to JsValue, which will work with preserve, and then in your code you can manually check if it's .is_undefined() or, if not, cast it to js_sys::Date.

@RReverser
Copy link
Owner

and then in your code you can manually check if it's .is_undefined() or, if not, cast it to js_sys::Date

You could also wrap this into a custom function compatible with deserialize_with. It would look roughly like this (untested):

fn deserialize_optional_date<'de, D: serde::Deserializer<'de>>(de: D) -> Result<Option<js_sys::Date>, D::Error> {
  let value: JsValue = serde_wasm_bindgen::preserve::deserialize(de)?;
  Ok(if value.is_undefined() {
    None
  } else {
    Some(value.dyn_into().map_err(|e| /* convert error */)?)
  })
}

/// ...

#[serde(deserialize_with = "deserialize_optional_date")]
pub testedTSAsDateTime: Option<js_sys::Date>,

Hope this helps.

@ronnybremer
Copy link
Author

Thank you Ingvar, I will try both approaches and update my findings here for the benefit of others.

@murar8
Copy link

murar8 commented Feb 2, 2024

Just released a small crate to help work around the issue: https://crates.io/crates/serde_nested_with
It will generate the workaround module for you.

@ronnybremer
Copy link
Author

Your best bet for now is probably to change the type of that field to JsValue, which will work with preserve, and then in your code you can manually check if it's .is_undefined() or, if not, cast it to js_sys::Date.

I used this solution for now, it works perfectly. Either setting the JsValue field to JsValue::NULL or a date.dyn_into().

@ronnybremer
Copy link
Author

Just released a small crate to help work around the issue: https://crates.io/crates/serde_nested_with It will generate the workaround module for you.

Thats amazing, thank you for your help! I have a few structs which do not require "skip_deserialize" so for those I will test the new crate.

@murar8
Copy link

murar8 commented Feb 3, 2024

Thats amazing, thank you for your help! I have a few structs which do not require "skip_deserialize" so for those I will test the new crate.

I updated the crate to support additional attributes, would be nice if you could give it a shot.

@ronnybremer
Copy link
Author

ronnybremer commented Feb 4, 2024

Thats amazing, thank you for your help! I have a few structs which do not require "skip_deserialize" so for those I will test the new crate.

I updated the crate to support additional attributes, would be nice if you could give it a shot.

This is great! I am using the following struct statement and it works like a charm:

struct TestStruc {
    #[serde_nested(sub = "js_sys::Date", serde(with = "serde_wasm_bindgen::preserve"))]
    #[serde(skip_deserializing)]
    pub testTSAsDateTime: Option<js_sys::Date>,
}

Thank you again, this is amazingly helpful and hopefully will find many users besides me.

@RReverser Maybe this could be added to the README?

@RReverser
Copy link
Owner

It's not really serde-wasm-bindgen-specific, I'd rather suggest opening a PR against Serde's own docs so that it would be on https://serde.rs/.

@RReverser
Copy link
Owner

Off-topic, but I'm curious @ronnybremer, in examples like

pub struct Params {
    #[serde(
        skip_deserializing,
        default = "js_sys::Date::new_0",
        with = "serde_wasm_bindgen::preserve"
    )]
    pub testedTSAsDateTime: js_sys::Date,
}

do you really need skip_deserializing on field-by-field basis? The name "params" seems to suggest that it only goes one direction and never needs to be deserialized, so maybe you can omit derive(Deserialize) altogether?

@ronnybremer
Copy link
Author

do you really need skip_deserializing on field-by-field basis? The name "params" seems to suggest that it only goes one direction and never needs to be deserialized, so maybe you can omit derive(Deserialize) altogether?

The example is misleading, for sure. I have a large struct coming in from Json via a Rest call, deserialized with Serde. However, I would need to add a few calculated fields to the structure before passing it onto JS. Like converting a UNIX timestamp into a JsDate. Since those fields are not coming form the server, Serde would complain and hence I skip them during deserialization.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants