使用 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
Traits
將 #[derive(Serialize, Deserialize)]
新增到您的類型。您的類型中的所有成員也必須受到 Serde 的支援,也就是說,它們的類型也必須實作 Serialize
和 Deserialize
traits。
例如,假設我們想要將這個 struct
傳遞給 JavaScript;在 wasm-bindgen
中,由於使用了 HashMap
、陣列和巢狀的 Vec
s,通常無法做到這一點。這些類型都不支援直接透過 wasm ABI 傳送,但它們都實作了 Serde 的 Serialize
和 Deserialize
。
請注意,我們不需要使用 #[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
將會是一個 Map
,field2
將會是一個 JavaScript Array
,其成員是數字的 Array
s,而 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_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_serde
和 JsValue::into_serde
)內建於 wasm-bindgen
本身。但是,這需要依賴 serde_json
,這有一個問題:啟用 serde_json
和其他 crate 的某些功能後,serde_json
將最終循環依賴於 wasm-bindgen
,這在 Rust 中是不合法的,並導致人們的程式碼無法編譯。因此,這些方法被提取到 gloo-utils
中,並帶有一個擴充 trait,而原始方法則被棄用。