-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Deprecate JsValue::from_serde
and JsValue::into_serde
#3031
Changes from all commits
7d0e486
874e006
9bda91a
3fde797
61766c4
f235bfe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -1,21 +1,19 @@ | ||||||||||
# Serializing and Deserializing Arbitrary Data Into and From `JsValue` with Serde | ||||||||||
|
||||||||||
It's possible to pass arbitrary data from Rust to JavaScript by serializing it | ||||||||||
to JSON with [Serde](https://github.com/serde-rs/serde). `wasm-bindgen` includes | ||||||||||
the `JsValue` type, which streamlines serializing and deserializing. | ||||||||||
with [Serde](https://github.com/serde-rs/serde). This can be done through the | ||||||||||
[`serde-wasm-bindgen`](https://docs.rs/serde-wasm-bindgen) crate. | ||||||||||
|
||||||||||
## Enable the `"serde-serialize"` Feature | ||||||||||
## Add dependencies | ||||||||||
|
||||||||||
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. | ||||||||||
To use `serde-wasm-bindgen`, you first have to add it as a dependency in your | ||||||||||
`Cargo.toml`. You also need the `serde` crate, with the `derive` feature | ||||||||||
enabled, to allow your types to be serialized and deserialized with Serde. | ||||||||||
|
||||||||||
```toml | ||||||||||
[dependencies] | ||||||||||
serde = { version = "1.0", features = ["derive"] } | ||||||||||
wasm-bindgen = { version = "0.2", features = ["serde-serialize"] } | ||||||||||
serde-wasm-bindgen = "0.4" | ||||||||||
``` | ||||||||||
|
||||||||||
## Derive the `Serialize` and `Deserialize` Traits | ||||||||||
|
@@ -42,7 +40,7 @@ pub struct Example { | |||||||||
} | ||||||||||
``` | ||||||||||
|
||||||||||
## Send it to JavaScript with `JsValue::from_serde` | ||||||||||
## Send it to JavaScript with `serde_wasm_bindgen::to_value` | ||||||||||
|
||||||||||
Here's a function that will pass an `Example` to JavaScript by serializing it to | ||||||||||
`JsValue`: | ||||||||||
|
@@ -58,28 +56,28 @@ pub fn send_example_to_js() -> JsValue { | |||||||||
field3: [1., 2., 3., 4.] | ||||||||||
}; | ||||||||||
|
||||||||||
JsValue::from_serde(&example).unwrap() | ||||||||||
serde_wasm_bindgen::to_value(&example).unwrap() | ||||||||||
} | ||||||||||
``` | ||||||||||
|
||||||||||
## Receive it from JavaScript with `JsValue::into_serde` | ||||||||||
## Receive it from JavaScript with `serde_wasm_bindgen::from_value` | ||||||||||
|
||||||||||
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(); | ||||||||||
pub fn receive_example_from_js(val: JsValue) { | ||||||||||
let example: Example = serde_wasm_bindgen::from_value(val).unwrap(); | ||||||||||
... | ||||||||||
} | ||||||||||
``` | ||||||||||
|
||||||||||
## JavaScript Usage | ||||||||||
|
||||||||||
In the `JsValue` that JavaScript gets, `field1` 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. | ||||||||||
In the `JsValue` that JavaScript gets, `field1` will be a `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"; | ||||||||||
|
@@ -94,23 +92,61 @@ example.field2.push([5, 6]); | |||||||||
receive_example_from_js(example); | ||||||||||
``` | ||||||||||
|
||||||||||
## An Alternative Approach: `serde-wasm-bindgen` | ||||||||||
|
||||||||||
[The `serde-wasm-bindgen` | ||||||||||
crate](https://github.com/cloudflare/serde-wasm-bindgen) serializes and | ||||||||||
deserializes Rust structures directly to `JsValue`s, without going through | ||||||||||
temporary JSON stringification. This approach has both advantages and | ||||||||||
disadvantages. | ||||||||||
|
||||||||||
The primary advantage is smaller code size: going through JSON entrenches code | ||||||||||
to stringify and parse floating point numbers, which is not a small amount of | ||||||||||
code. It also supports more types than JSON does, such as `Map`, `Set`, and | ||||||||||
array buffers. | ||||||||||
|
||||||||||
There are two primary disadvantages. The first is that it is not always | ||||||||||
compatible with the default JSON-based serialization. The second is that it | ||||||||||
performs more calls back and forth between JS and Wasm, which has not been fully | ||||||||||
optimized in all engines, meaning it can sometimes be a speed | ||||||||||
regression. However, in other cases, it is a speed up over the JSON-based | ||||||||||
stringification, so — as always — make sure to profile your own use | ||||||||||
cases as necessary. | ||||||||||
## An alternative approach - using JSON | ||||||||||
|
||||||||||
`serde-wasm-bindgen` works by directly manipulating JavaScript values. This | ||||||||||
requires a lot of calls back and forth between Rust and JavaScript, which can | ||||||||||
sometimes be slow. An alternative way of doing this is to serialize values to | ||||||||||
JSON, and then parse them on the other end. Browsers' JSON implementations are | ||||||||||
usually quite fast, and so this approach can outstrip `serde-wasm-bindgen`'s | ||||||||||
performance in some cases. | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
... addressing @RReverser's point about type support There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, so this edit didn't get in? Can you make a separate PR please? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Voilà! #3047 |
||||||||||
|
||||||||||
That's not to say that using JSON is always faster, though - the JSON approach | ||||||||||
can be anywhere from 2x to 0.2x the speed of `serde-wasm-bindgen`, depending on | ||||||||||
the JS runtime and the values being passed. It also leads to larger code size | ||||||||||
than `serde-wasm-bindgen`. So, make sure to profile each for your own use | ||||||||||
cases. | ||||||||||
|
||||||||||
This approach is implemented in [`gloo_utils::format::JsValueSerdeExt`]: | ||||||||||
|
||||||||||
```toml | ||||||||||
# Cargo.toml | ||||||||||
[dependencies] | ||||||||||
gloo-utils = { version = "0.1", features = ["serde"] } | ||||||||||
``` | ||||||||||
|
||||||||||
```rust | ||||||||||
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(); | ||||||||||
... | ||||||||||
} | ||||||||||
``` | ||||||||||
|
||||||||||
[`gloo_utils::format::JsValueSerdeExt`]: https://docs.rs/gloo-utils/latest/gloo_utils/format/trait.JsValueSerdeExt.html | ||||||||||
|
||||||||||
## History | ||||||||||
|
||||||||||
In previous versions of `wasm-bindgen`, `gloo-utils`'s JSON-based Serde support | ||||||||||
(`JsValue::from_serde` and `JsValue::into_serde`) was built into `wasm-bindgen` | ||||||||||
itself. However, this required a dependency on `serde_json`, which had a | ||||||||||
problem: with certain features of `serde_json` and other crates enabled, | ||||||||||
`serde_json` would end up with a circular dependency on `wasm-bindgen`, which | ||||||||||
is illegal in Rust and caused people's code to fail to compile. So, these | ||||||||||
methods were extracted out into `gloo-utils` with an extension trait and the | ||||||||||
originals were deprecated. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like it's important to still call out the limitations in terms of type support in JSON-based approach here too (like the document did in the original version).
The new version of this section makes it sound like both approaches are equivalent and the only difference is performance. However, the support for various JavaScript types that are not representable by JSON is an even more important advantage of serde-wasm-bindgen than just runtime perf or code size IMO.