使用 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;在 wasm-bindgen 中,由於使用了 HashMap、陣列和巢狀的 Vec,通常無法做到這一點。這些類型都不支援直接透過 wasm ABI 傳送,但它們都實作了 Serde 的 SerializeDeserialize

請注意,我們不需要使用 #[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 會是 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)]
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 中,並且原始方法已被棄用。