使用 Serde 将任意数据序列化和反序列化为和从 JsValue

可以通过使用 Serde 将任意数据从 Rust 传递到 JavaScript 进行序列化。这可以通过 serde-wasm-bindgen crate 来完成。

添加依赖项

要使用 serde-wasm-bindgen,您首先必须将其作为依赖项添加到您的 Cargo.toml 中。您还需要启用 derive 功能的 serde crate,以允许您的类型使用 Serde 进行序列化和反序列化。

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.4"

派生 SerializeDeserialize 特性

#[derive(Serialize, Deserialize)] 添加到您的类型中。您的类型的所有成员也必须受 Serde 支持,即它们的类型也必须实现 SerializeDeserialize 特性。

例如,假设我们想将这个 struct 传递给 JavaScript;由于使用了 HashMap、数组和嵌套的 Vec,通常在 wasm-bindgen 中无法这样做。这些类型都不支持在 wasm ABI 上以原始方式发送,但它们都实现了 Serde 的 SerializeDeserialize

请注意,我们不需要使用 #[wasm_bindgen] 宏。


# #![allow(unused_variables)]
#fn main() {
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 发送到 JavaScript

这是一个将 Example 通过将其序列化为 JsValue 传递给 JavaScript 的函数


# #![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.]
    };

    serde_wasm_bindgen::to_value(&example).unwrap()
}
#}

使用 serde_wasm_bindgen::from_value 从 JavaScript 接收

这是一个将从 JavaScript 接收 JsValue 参数,然后从中反序列化 Example 的函数


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

JavaScript 用法

在 JavaScript 获取的 JsValue 中,field1 将是一个 Mapfield2 将是一个 JavaScript Array,其成员是数字 Array,而 field3 将是一个数字 Array

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);

另一种方法 - 使用 JSON

serde-wasm-bindgen 通过直接操作 JavaScript 值来工作。这需要在 Rust 和 JavaScript 之间进行大量的来回调用,有时可能会很慢。另一种方法是将值序列化为 JSON,然后在另一端解析它们。浏览器的 JSON 实现通常非常快,因此在某些情况下,这种方法可以超越 serde-wasm-bindgen 的性能。但是这种方法仅支持可以序列化为 JSON 的类型,省略了 serde-wasm-bindgen 支持的一些重要类型,例如 MapSet 和数组缓冲区。

但这并不是说使用 JSON 总是更快,尽管如此 - JSON 方法的速度可以是 serde-wasm-bindgen 的 2 倍到 0.2 倍,具体取决于 JS 运行时和传递的值。它还导致比 serde-wasm-bindgen 更大的代码大小。因此,请务必针对您自己的用例进行分析。

此方法在 gloo_utils::format::JsValueSerdeExt 中实现

# Cargo.toml
[dependencies]
gloo-utils = { version = "0.1", features = ["serde"] }

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

历史

在先前版本的 wasm-bindgen 中,gloo-utils 基于 JSON 的 Serde 支持(JsValue::from_serdeJsValue::into_serde)内置于 wasm-bindgen 本身。然而,这需要依赖于 serde_json,这有一个问题:启用 serde_json 和其他 crate 的某些功能后,serde_json 将最终对 wasm-bindgen 产生循环依赖,这在 Rust 中是非法的,并导致人们的代码无法编译。因此,这些方法被提取到 gloo-utils 中,并带有扩展特性,原始方法已被弃用。