guide: Polish Serializing + Deserializing into/from JsValue with Serde section

This commit is contained in:
Nick Fitzgerald
2018-08-07 14:24:26 -07:00
parent 676611020e
commit 2e7620e014
3 changed files with 108 additions and 73 deletions

View File

@ -12,7 +12,7 @@
- [Passing Rust Closures to JS](./reference/passing-rust-closures-to-js.md)
- [Receiving JS Closures in Rust](./reference/receiving-js-closures-in-rust.md)
- [No ES Modules](./reference/no-esm.md)
- [Passing Arbitrary data](./reference/passing-data.md)
- [Arbitrary Data with Serde](./reference/arbitrary-data-with-serde.md)
- [Command Line Interface](./reference/cli.md)
- [Supported Types](./reference/types.md)
- [`#[wasm_bindgen]` Attributes](./reference/attributes/index.md)

View File

@ -0,0 +1,107 @@
# Serializing and Deserializing Arbitrary Data Into and From `JsValue` with Serde
It's possible to pass arbirtrary data from Rust to JavaScript by serializing it
with [Serde](https://github.com/serde-rs/serde). `wasm-bindgen` includes the
`JsValue` type, which streamlines serializing and deserializing.
## Enable the `"serde-serialize"` Feature
To enable the `"serde-serialize"` feature, do two things in `Cargo.toml`:
1. Add the `serde` and `serde_derive` crates to `[dependencies]`.
2. Add `features = ["serde-serialize"]` to the existing `wasm-bindgen`
dependency.
```toml
[dependencies]
serde = "^1.0.59"
serde_derive = "^1.0.59"
[dependencies.wasm-bindgen]
version = "^0.2"
features = ["serde-serialize"]
```
## Import Serde's Custom-Derive Macros
In your top-level Rust file (e.g. `lib.rs` or `main.rs`), enable the `Serialize`
and `Deserialize` custom-derive macros:
```rust
#[macro_use]
extern crate serde_derive;
```
## Derive the `Serialize` and `Deserialize` Traits
Add `#![derive(Serialize, Deserialize)]` to your type. All of your type's
members must also be supported by Serde, i.e. their types must also implement
the `Serialize` and `Deserialize` traits.
For example, let's say we'd like to pass this `struct` to JavaScript; doing so
is not possible in `wasm-bindgen` normally due to the use of `HashMap`s, arrays,
and nested `Vec`s. None of those types are supported for sending across the wasm
ABI naively, but all of them implement Serde's `Serialize` and `Deserialize`.
Note that we do not need to use the `#[wasm_bindgen]` macro.
```rust
#[derive(Serialize)]
pub struct Example {
pub field1: HashMap<u32, String>,
pub field2: Vec<Vec<f32>>,
pub field3: [f32; 4],
}
```
## Send it to JavaScript with `JsValue::from_serde`
Here's a function that will pass an `Example` to JavaScript by serializing it to
`JsValue`:
```rust
#[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()
}
```
## Receive it from JavaScript with `JsValue::into_serde`
Here's a function that will receive a `JsValue` parameter from JavaScript and
then deserialize an `Example` from it:
```rust
#[wasm_bindgen]
pub fn receive_example_from_js(val: &JsValue) {
let example: Example = val.into_serde().unwrap();
...
}
```
## JavaScript Usage
In the `JsValue` that JavaScript gets, `fied1` will be an `Object` (not a
JavaScript `Map`), `field2` will be a JavaScript `Array` whose members are
`Array`s of numbers, and `field3` will be an `Array` of numbers.
```js
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);
```

View File

@ -1,72 +0,0 @@
# Passing arbitrary data to JS
It's possible to pass arbirtrary data from Rust to JavaScript by serializing it
with [Serde](https://github.com/serde-rs/serde).
`wasm-bindgen` includes the `JsValue` type, which streamlines serializing and deserializing.
In order accomplish this, we must include the serde and serde_derive
crates in `Cargo.toml`, and configure `wasm-bindgen` to work with this feature:
Cargo.toml
```toml
[dependencies]
serde = "^1.0.59"
serde_derive = "^1.0.59"
[dependencies.wasm-bindgen]
version = "^0.2"
features = ["serde-serialize"]
```
In our top-level Rust file (eg `lib.rs` or `main.rs`), we enable the `Serialize`
macro:
```rust
#[macro_use]
extern crate serde_derive;
```
The data we pass at all nesting levels must be supported by serde, or be a `struct` or `enum` that
derives the Serialize trait. For example, let's say we'd like to pass this
struct to JS; doing so is not possible in bindgen directly due to the use
of public fields, `HashMap`s, arrays, and nested `Vec`s. Note that we do not
need to use the `#[wasm_bindgen]` macro.
```rust
#[derive(Serialize)]
pub struct Example {
pub field1: HashMap<u32, String>,
pub field2: Vec<Vec<f32>>,
pub field3: [f32; 4],
}
```
Here's a function that will pass an instance of this `struct` to JS:
```rust
#[wasm_bindgen]
pub fn pass_example() -> 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()
}
```
When calling this function from JS, its output will automatically be deserialized.
In this example, `fied1` will be a JS `object` (Not a JS `Map`), `field2` will be a
2d JS `array`, and `field3` will be a 1d JS `array`. Example calling code:
```typescript
const rust = import("./from_rust");
rust.then(
r => {
console.log(r.pass_example())
}
)
```