Skip to content

[BUG] Queue: typed MessageBatch<T> deserialize via serde_wasm_bindgen::from_value mangles payloads — offer a JSON-based path #1013

Description

@Ujang360

Is there an existing issue for this?

  • I have searched the existing issues

What version of workers-rs are you using?

0.8.4

What version of wrangler are you using?

4.100.0

Describe the bug

Summary

The typed queue-consumer path (MessageBatch<T> / Message::try_from(RawMessage)) deserializes each body with serde_wasm_bindgen::from_value. For real-world payloads we found its V8↔serde type coercion unreliable — it produces wrong data or an opaque deserialize error. Bypassing it with JSON.stringify(body) + serde_json::from_str works reliably for the exact same payloads.

Environment

  • worker 0.8.4, wasm32-unknown-unknown, Rust 1.96.
  • Payload: a struct with serde_json::Values and tuples, e.g. substitutes: Vec<(String, serde_json::Value)>, plus Vec<String>, strings.

Reliable workaround (consumer side)

use js_sys::JSON;
fn parse<T: serde::de::DeserializeOwned>(body: &JsValue) -> Result<T, String> {
    let s = JSON::stringify(body).ok().and_then(|s| s.as_string())
        .ok_or("JSON.stringify produced no string")?;
    serde_json::from_str(&s).map_err(|e| e.to_string())
}

Round-trips through plain JSON and Just Works where serde_wasm_bindgen::from_value mangled the data. (This is also what the previous generation of our consumer hand-rolled — the footgun is long-standing.)

Asks

  • Document the limitation + this JSON.stringify + serde_json escape hatch in the Queues guide — it was the single biggest footgun we hit.
  • Consider a content_type-aware / JSON-based deserialize helper on MessageBatch / Message, e.g. msg.body_json::<T>(), that uses JSON.stringify + serde_json for json-typed bodies instead of serde_wasm_bindgen::from_value.
  • More generally: surface the message's content_type to the consumer so it can choose the right decoder.

Steps To Reproduce

// worker-0.8.4/src/queue.rs
impl<T> TryFrom<RawMessage> for Message<T> where T: DeserializeOwned {
    fn try_from(value: RawMessage) -> Result<Self, Error> {
        let body = serde_wasm_bindgen::from_value(value.body())?; // <-- V8 coercion
        ...
    }
}

serde_wasm_bindgen's coercion misbehaves on serde_json::Value, tuples (Vec<(String, Value)>), maps, and number / undefined edge cases.

Related to #1012

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions