Serializing and Deserializing Arbitrary Data Into and From JsValue with Serde

It's possible to pass arbirtrary data from Rust to JavaScript by serializing it with Serde. wasm-bindgen includes the JsValue type, which streamlines serializing and deserializing.

Enable the "serde-serialize" Feature

To enable the "serde-serialize" feature, do two things in Cargo.toml:

  1. Add the serde and serde_derive crates to [dependencies].
  2. Add features = ["serde-serialize"] to the existing wasm-bindgen dependency.
[dependencies]
serde = "^1.0.59"
serde_derive = "^1.0.59"

[dependencies.wasm-bindgen]
version = "^0.2"
features = ["serde-serialize"]

Import Serde's Custom-Derive Macros

In your top-level Rust file (e.g. lib.rs or main.rs), enable the Serialize and Deserialize custom-derive macros:


# #![allow(unused_variables)]
#fn main() {
#[macro_use]
extern crate serde_derive;
#}

Derive the Serialize and Deserialize Traits

Add #![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.


# #![allow(unused_variables)]
#fn main() {
#[derive(Serialize)]
pub struct Example {
    pub field1: HashMap<u32, String>,
    pub field2: Vec<Vec<f32>>,
    pub field3: [f32; 4],
}
#}

Send it to JavaScript with JsValue::from_serde

Here's a function that will pass an Example to JavaScript by serializing it to JsValue:


# #![allow(unused_variables)]
#fn main() {
#[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()
}
#}

Receive it from JavaScript with JsValue::into_serde

Here's a function that will receive a JsValue parameter from JavaScript and then deserialize an Example from it:


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen]
pub fn receive_example_from_js(val: &JsValue) {
    let example: Example = val.into_serde().unwrap();
    ...
}
#}

JavaScript Usage

In the JsValue that JavaScript gets, field1 will be an Object (not a JavaScript 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);