使用 Serde 将任意数据序列化和反序列化到 JsValue
中
可以使用 Serde 将任意数据从 Rust 传递到 JavaScript。这可以通过 serde-wasm-bindgen
crate 完成。
添加依赖项
要使用 serde-wasm-bindgen
,首先需要在 Cargo.toml
中将其添加为依赖项。还需要 serde
crate,并启用 derive
特性,以允许您的类型使用 Serde 进行序列化和反序列化。
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.4"
派生 Serialize
和 Deserialize
特性
在您的类型中添加 #[derive(Serialize, Deserialize)]
。您类型的所有成员也必须受 Serde 支持,即它们的类型也必须实现 Serialize
和 Deserialize
特性。
例如,假设我们要将此 struct
传递到 JavaScript;由于使用了 HashMap
、数组和嵌套的 Vec
,因此在 wasm-bindgen
中通常无法做到这一点。这些类型都不支持直接跨 wasm ABI 发送,但它们都实现了 Serde 的 Serialize
和 Deserialize
。
请注意,我们不需要使用 #[wasm_bindgen]
宏。
#![allow(unused)] 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
传递到 JavaScript 的函数,通过将其序列化为 JsValue
来实现。
#![allow(unused)] 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)] 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
将是一个 Map
,field2
将是一个 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
支持的一些重要类型,例如 Map
、Set
和数组缓冲区。
但这并不意味着使用 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)] 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_serde
和 JsValue::into_serde
)内置于 wasm-bindgen
本身。但是,这需要依赖 serde_json
,而 serde_json
存在一个问题:当启用 serde_json
和其他 crate 的某些功能时,serde_json
最终会对 wasm-bindgen
产生循环依赖,这在 Rust 中是非法的,导致人们的代码无法编译。因此,这些方法被提取到 gloo-utils
中,并使用扩展 trait,原始方法被弃用。