guide/src/reference/arbitrary-data-with-serde.md
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 HashMaps, arrays,
and nested Vecs. 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_valueHere'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_valueHere'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 Arrays 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.