使用 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"
派生 Serialize
與 Deserialize
特性
將 #[derive(Serialize, Deserialize)]
新增到您的類型。您類型中的所有成員也必須受到 Serde 的支援,也就是說,它們的類型也必須實作 Serialize
與 Deserialize
特性。
例如,假設我們想將這個 struct
傳遞給 JavaScript;在 wasm-bindgen
中,由於使用了 HashMap
、陣列和巢狀的 Vec
,通常無法做到這一點。這些類型都不支援直接透過 wasm ABI 傳送,但它們都實作了 Serde 的 Serialize
與 Deserialize
。
請注意,我們不需要使用 #[wasm_bindgen]
macro。
#![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
序列化為 JsValue
並傳遞給 JavaScript 的函式
#![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
和其他 crate 的某些功能後,serde_json
將會對 wasm-bindgen
產生循環依賴,這在 Rust 中是非法的,並導致人們的程式碼無法編譯。因此,這些方法被提取到具有擴充特性的 gloo-utils
中,並且原始方法已被棄用。