serde-wasm-bindgen icon indicating copy to clipboard operation
serde-wasm-bindgen copied to clipboard

rust struct with #[serde(flatten)] loses data when converting to JsValue

Open kirito41dd opened this issue 1 month ago • 5 comments

tracing::info!("to json {:?}", serde_json::to_string(&v).unwrap());
tracing::info!("to value {:?}", serde_wasm_bindgen::to_value(&v).unwrap());
to json "{\"method\":\"__stream/expmple\",\"params\":{\"data\":\"hi i'm server(2)\"}}"
to value JsValue(Object({"method":"__stream/expmple","params":{}}))

https://github.com/modelcontextprotocol/rust-sdk/blob/main/crates/rmcp/src/model.rs#L372

#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Notification<M = String, P = JsonObject> {
    pub method: M,
    pub params: P,
    /// extensions will carry anything possible in the context, including [`Meta`]
    ///
    /// this is similar with the Extensions in `http` crate
    #[cfg_attr(feature = "schemars", schemars(skip))]
    pub extensions: Extensions,
}

impl<M, R> Serialize for Notification<M, R>
where
    M: Serialize,
    R: Serialize,
{
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let extensions = &self.extensions;
        let _meta = extensions.get::<Meta>().map(Cow::Borrowed);
        Proxy::serialize(
            &Proxy {
                method: &self.method,
                params: WithMeta {
                    _rest: &self.params,
                    _meta,
                },
            },
            serializer,
        )
    }
}

#[derive(Serialize, Deserialize)]
struct WithMeta<'a, P> {
    #[serde(skip_serializing_if = "Option::is_none")]
    _meta: Option<Cow<'a, Meta>>,
    #[serde(flatten)]
    _rest: P,
}

#[derive(Serialize, Deserialize)]
struct Proxy<'a, M, P> {
    method: M,
    params: WithMeta<'a, P>,
}

kirito41dd avatar Dec 04 '25 13:12 kirito41dd

Unfortunately, it's a known long-standing issue in Serde itself. https://github.com/serde-rs/serde/issues/1183

Nothing we can do on specific deserializer side.

RReverser avatar Dec 04 '25 15:12 RReverser

Thanks, But the problem I described occurs in the serializer.

kirito41dd avatar Dec 05 '25 06:12 kirito41dd

You are right, sorry, I overlooked that.

Can you provide a minimal repro that works in isolation? Eg in your code snippet it's unclear where data field comes from, ideally I'd like so see all struct with no 3rd-party deps.

RReverser avatar Dec 05 '25 13:12 RReverser

https://github.com/kirito41dd/serde-wasm-bindgen-issue

@RReverser thanks.

kirito41dd avatar Dec 07 '25 14:12 kirito41dd

I suspect this has to do with our checks where we tried to ensure the JsValue specific conversion only works with serde-wasm-bindgen and doesn't try to produce gibberish with other formats... But serde flatten generating serialize_map instead of serialize_struct disrupts that.

RReverser avatar Dec 07 '25 14:12 RReverser