JsValue
with SerdeIt's possible to pass arbitrary data from Rust to JavaScript by serializing it with Serde. This can be done through the serde-wasm-bindgen
crate.
To use serde-wasm-bindgen
, you first have to add it as a dependency in your Cargo.toml
. You also need the serde
crate, with the derive
feature enabled, to allow your types to be serialized and deserialized with Serde.
[dependencies] serde = { version = "1.0", features = ["derive"] } serde-wasm-bindgen = "0.4"
Serialize
and Deserialize
TraitsAdd #[derive(Serialize, Deserialize)]
to your type. All of your type's members must also be supported by Serde, i.e. their types must also implement the Serialize
and Deserialize
traits.
For example, let‘s say we’d like to pass this struct
to JavaScript; doing so is not possible in wasm-bindgen
normally due to the use of HashMap
s, arrays, and nested Vec
s. None of those types are supported for sending across the wasm ABI naively, but all of them implement Serde's Serialize
and Deserialize
.
Note that we do not need to use the #[wasm_bindgen]
macro.
use serde::{Serialize, Deserialize}; #[derive(Serialize, Deserialize)] pub struct Example { pub field1: HashMap<u32, String>, pub field2: Vec<Vec<f32>>, pub field3: [f32; 4], }
serde_wasm_bindgen::to_value
Here's a function that will pass an Example
to JavaScript by serializing it to JsValue
:
#[wasm_bindgen] pub fn send_example_to_js() -> JsValue { let mut field1 = HashMap::new(); field1.insert(0, String::from("ex")); let example = Example { field1, field2: vec![vec![1., 2.], vec![3., 4.]], field3: [1., 2., 3., 4.] }; serde_wasm_bindgen::to_value(&example).unwrap() }
serde_wasm_bindgen::from_value
Here's a function that will receive a JsValue
parameter from JavaScript and then deserialize an Example
from it:
#[wasm_bindgen] pub fn receive_example_from_js(val: JsValue) { let example: Example = serde_wasm_bindgen::from_value(val).unwrap(); ... }
In the JsValue
that JavaScript gets, field1
will be a Map
, field2
will be a JavaScript Array
whose members are Array
s of numbers, and field3
will be an Array
of numbers.
import { send_example_to_js, receive_example_from_js } from "example"; // Get the example object from wasm. let example = send_example_to_js(); // Add another "Vec" element to the end of the "Vec<Vec<f32>>" example.field2.push([5, 6]); // Send the example object back to wasm. receive_example_from_js(example);
serde-wasm-bindgen
works by directly manipulating JavaScript values. This requires a lot of calls back and forth between Rust and JavaScript, which can sometimes be slow. An alternative way of doing this is to serialize values to JSON, and then parse them on the other end. Browsers' JSON implementations are usually quite fast, and so this approach can outstrip serde-wasm-bindgen
's performance in some cases. But this approach supports only types that can be serialized as JSON, leaving out some important types that serde-wasm-bindgen
supports such as Map
, Set
, and array buffers.
That's not to say that using JSON is always faster, though - the JSON approach can be anywhere from 2x to 0.2x the speed of serde-wasm-bindgen
, depending on the JS runtime and the values being passed. It also leads to larger code size than serde-wasm-bindgen
. So, make sure to profile each for your own use cases.
This approach is implemented in gloo_utils::format::JsValueSerdeExt
:
# Cargo.toml [dependencies] gloo-utils = { version = "0.1", features = ["serde"] }
use gloo_utils::format::JsValueSerdeExt; #[wasm_bindgen] pub fn send_example_to_js() -> JsValue { let mut field1 = HashMap::new(); field1.insert(0, String::from("ex")); let example = Example { field1, field2: vec![vec![1., 2.], vec![3., 4.]], field3: [1., 2., 3., 4.] }; JsValue::from_serde(&example).unwrap() } #[wasm_bindgen] pub fn receive_example_from_js(val: JsValue) { let example: Example = val.into_serde().unwrap(); ... }
In previous versions of wasm-bindgen
, gloo-utils
‘s JSON-based Serde support (JsValue::from_serde
and JsValue::into_serde
) was built into wasm-bindgen
itself. However, this required a dependency on serde_json
, which had a problem: with certain features of serde_json
and other crates enabled, serde_json
would end up with a circular dependency on wasm-bindgen
, which is illegal in Rust and caused people’s code to fail to compile. So, these methods were extracted out into gloo-utils
with an extension trait and the originals were deprecated.