Deprecate JsValue::from_serde and JsValue::into_serde (#3031)

* Deprecate `JsValue::from_serde` and `JsValue::into_serde`

I've listed `serde-wasm-bindgen` as the replacement, and changed the section of the guide that talks about Serde to talk about `serde-wasm-bindgen` instead of the deprecated methods.

I didn't remove it entirely because I can imagine someone remembering it and trying to look it back up, only to find that it no longer exists, which would quite frustrating. I also added a footnote about the deprecated methods in case someone remembers the old way and wants to know what happened.

There were several examples using `from_serde`/`into_serde`, which I updated to use `serde-wasm-bindgen` or not use `serde` altogether.

The `fetch` example was a bit weird, in that it took a JS value, parsed it into a Rust value, only to serialize it back into a JS value. I removed that entirely in favour of just passing the original JS value directly. I suppose it behaves slightly differently in that it loses the extra validation, but a panic isn't all that much better than a JS runtime error.

* fmt

* Mention JSON as an alternative to `serde-wasm-bindgen`

* Use `gloo-utils` instead of raw `JSON`

I was considering leaving the examples using `JSON` directly and mentioning `gloo-utils` as an aside, but that has the major footgun that `JSON.stringify(undefined) === undefined`, causing a panic when deserializing `undefined` since the return type of `JSON::stringify` isn't optional. `gloo-utils` works around this, so I recommended it instead.

* Mention `gloo-utils` in API docs

* Rephrase section about deprecated methods
This commit is contained in:
Liam Murphy 2022-08-30 14:17:27 +10:00 committed by GitHub
parent b2f9e1247b
commit 823f57698c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 107 additions and 90 deletions

View File

@ -8,11 +8,9 @@ edition = "2018"
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = { version = "0.2.82", features = ["serde-serialize"] }
wasm-bindgen = "0.2.82"
js-sys = "0.3.59"
wasm-bindgen-futures = "0.4.32"
serde = { version = "1.0.80", features = ["derive"] }
serde_derive = "^1.0.59"
[dependencies.web-sys]
version = "0.3.4"

View File

@ -1,37 +1,8 @@
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
use web_sys::{Request, RequestInit, RequestMode, Response};
/// A struct to hold some data from the github Branch API.
///
/// Note how we don't have to define every member -- serde will ignore extra
/// data when deserializing
#[derive(Debug, Serialize, Deserialize)]
pub struct Branch {
pub name: String,
pub commit: Commit,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Commit {
pub sha: String,
pub commit: CommitDetails,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct CommitDetails {
pub author: Signature,
pub committer: Signature,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Signature {
pub name: String,
pub email: String,
}
#[wasm_bindgen]
pub async fn run(repo: String) -> Result<JsValue, JsValue> {
let mut opts = RequestInit::new();
@ -56,9 +27,6 @@ pub async fn run(repo: String) -> Result<JsValue, JsValue> {
// Convert this other `Promise` into a rust `Future`.
let json = JsFuture::from(resp.json()?).await?;
// Use serde to parse the JSON into a struct.
let branch_info: Branch = json.into_serde().unwrap();
// Send the `Branch` struct back to JS as an `Object`.
Ok(JsValue::from_serde(&branch_info).unwrap())
// Send the JSON response back to JS.
Ok(json)
}

View File

@ -13,8 +13,9 @@ js-sys = "0.3.59"
rayon = "1.1.0"
rayon-core = "1.5.0"
raytracer = { git = 'https://github.com/alexcrichton/raytracer', branch = 'update-deps' }
serde-wasm-bindgen = "0.4.3"
futures-channel-preview = "0.3.0-alpha.18"
wasm-bindgen = { version = "0.2.82", features = ['serde-serialize'] }
wasm-bindgen = "0.2.82"
wasm-bindgen-futures = "0.4.32"
[dependencies.web-sys]

View File

@ -28,11 +28,10 @@ impl Scene {
/// Creates a new scene from the JSON description in `object`, which we
/// deserialize here into an actual scene.
#[wasm_bindgen(constructor)]
pub fn new(object: &JsValue) -> Result<Scene, JsValue> {
pub fn new(object: JsValue) -> Result<Scene, JsValue> {
console_error_panic_hook::set_once();
Ok(Scene {
inner: object
.into_serde()
inner: serde_wasm_bindgen::from_value(object)
.map_err(|e| JsValue::from(e.to_string()))?,
})
}

View File

@ -10,10 +10,8 @@ crate-type = ["cdylib"]
[dependencies]
futures = "0.3.4"
js-sys = "0.3.59"
wasm-bindgen = {version = "0.2.82", features = ["serde-serialize"]}
wasm-bindgen = "0.2.82"
wasm-bindgen-futures = "0.4.32"
serde = { version = "1.0.80", features = ["derive"] }
serde_derive = "^1.0.59"
[dependencies.web-sys]
version = "0.3.36"

View File

@ -3,16 +3,13 @@
#[macro_use]
mod utils;
use futures::{future, Future};
use js_sys::Promise;
use js_sys::{Object, Promise, Reflect};
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use utils::set_panic_hook;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::future_to_promise;
use wasm_bindgen_futures::JsFuture;
use web_sys::*;
// A macro to provide `println!(..)`-style syntax for `console.log` logging.
@ -39,12 +36,16 @@ pub fn create_webgl_context(xr_mode: bool) -> Result<WebGl2RenderingContext, JsV
.unwrap();
let gl: WebGl2RenderingContext = if xr_mode {
let mut gl_attribs = HashMap::new();
gl_attribs.insert(String::from("xrCompatible"), true);
let js_gl_attribs = JsValue::from_serde(&gl_attribs).unwrap();
let gl_attribs = Object::new();
Reflect::set(
&gl_attribs,
&JsValue::from_str("xrCompatible"),
&JsValue::TRUE,
)
.unwrap();
canvas
.get_context_with_context_options("webgl2", &js_gl_attribs)?
.get_context_with_context_options("webgl2", &gl_attribs)?
.unwrap()
.dyn_into()?
} else {
@ -77,7 +78,6 @@ impl XrApp {
pub fn init(&self) -> Promise {
log!("Starting WebXR...");
let navigator: web_sys::Navigator = web_sys::window().unwrap().navigator();
let gpu = navigator.gpu();
let xr = navigator.xr();
let session_mode = XrSessionMode::Inline;
let session_supported_promise = xr.is_session_supported(session_mode);
@ -121,7 +121,7 @@ impl XrApp {
let g = f.clone();
let mut i = 0;
*g.borrow_mut() = Some(Closure::new(move |time: f64, frame: XrFrame| {
*g.borrow_mut() = Some(Closure::new(move |_time: f64, frame: XrFrame| {
log!("Frame rendering...");
if i > 2 {
log!("All done!");

View File

@ -11,8 +11,7 @@ then parses the resulting JSON.
## `Cargo.toml`
The `Cargo.toml` enables a number of features related to the `fetch` API and
types used: `Headers`, `Request`, etc. It also enables `wasm-bindgen`'s `serde`
support.
types used: `Headers`, `Request`, etc.
```toml
{{#include ../../../examples/fetch/Cargo.toml}}

View File

@ -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`
## An alternative approach - using JSON
[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.
`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.
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.
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.
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 &mdash; as always &mdash; make sure to profile your own use
cases as necessary.
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.

View File

@ -201,6 +201,14 @@ impl JsValue {
/// Creates a new `JsValue` from the JSON serialization of the object `t`
/// provided.
///
/// **This function is deprecated**, due to [creating a dependency cycle in
/// some circumstances][dep-cycle-issue]. Use [`serde-wasm-bindgen`] or
/// [`gloo_utils::format::JsValueSerdeExt`] instead.
///
/// [dep-cycle-issue]: https://github.com/rustwasm/wasm-bindgen/issues/2770
/// [`serde-wasm-bindgen`]: https://docs.rs/serde-wasm-bindgen
/// [`gloo_utils::format::JsValueSerdeExt`]: https://docs.rs/gloo-utils/latest/gloo_utils/format/trait.JsValueSerdeExt.html
///
/// This function will serialize the provided value `t` to a JSON string,
/// send the JSON string to JS, parse it into a JS object, and then return
/// a handle to the JS object. This is unlikely to be super speedy so it's
@ -214,6 +222,7 @@ impl JsValue {
///
/// Returns any error encountered when serializing `T` into JSON.
#[cfg(feature = "serde-serialize")]
#[deprecated = "causes dependency cycles, use `serde-wasm-bindgen` or `gloo_utils::format::JsValueSerdeExt` instead"]
pub fn from_serde<T>(t: &T) -> serde_json::Result<JsValue>
where
T: serde::ser::Serialize + ?Sized,
@ -225,6 +234,14 @@ impl JsValue {
/// Invokes `JSON.stringify` on this value and then parses the resulting
/// JSON into an arbitrary Rust value.
///
/// **This function is deprecated**, due to [creating a dependency cycle in
/// some circumstances][dep-cycle-issue]. Use [`serde-wasm-bindgen`] or
/// [`gloo_utils::format::JsValueSerdeExt`] instead.
///
/// [dep-cycle-issue]: https://github.com/rustwasm/wasm-bindgen/issues/2770
/// [`serde-wasm-bindgen`]: https://docs.rs/serde-wasm-bindgen
/// [`gloo_utils::format::JsValueSerdeExt`]: https://docs.rs/gloo-utils/latest/gloo_utils/format/trait.JsValueSerdeExt.html
///
/// This function will first call `JSON.stringify` on the `JsValue` itself.
/// The resulting string is then passed into Rust which then parses it as
/// JSON into the resulting value.
@ -236,6 +253,7 @@ impl JsValue {
///
/// Returns any error encountered when parsing the JSON into a `T`.
#[cfg(feature = "serde-serialize")]
#[deprecated = "causes dependency cycles, use `serde-wasm-bindgen` or `gloo_utils::format::JsValueSerdeExt` instead"]
pub fn into_serde<T>(&self) -> serde_json::Result<T>
where
T: for<'a> serde::de::Deserialize<'a>,