Skip to content

Commit 823f576

Browse files
authored
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
1 parent b2f9e12 commit 823f576

File tree

9 files changed

+110
-93
lines changed

9 files changed

+110
-93
lines changed

examples/fetch/Cargo.toml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,9 @@ edition = "2018"
88
crate-type = ["cdylib"]
99

1010
[dependencies]
11-
wasm-bindgen = { version = "0.2.82", features = ["serde-serialize"] }
11+
wasm-bindgen = "0.2.82"
1212
js-sys = "0.3.59"
1313
wasm-bindgen-futures = "0.4.32"
14-
serde = { version = "1.0.80", features = ["derive"] }
15-
serde_derive = "^1.0.59"
1614

1715
[dependencies.web-sys]
1816
version = "0.3.4"

examples/fetch/src/lib.rs

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,8 @@
1-
use serde::{Deserialize, Serialize};
21
use wasm_bindgen::prelude::*;
32
use wasm_bindgen::JsCast;
43
use wasm_bindgen_futures::JsFuture;
54
use web_sys::{Request, RequestInit, RequestMode, Response};
65

7-
/// A struct to hold some data from the github Branch API.
8-
///
9-
/// Note how we don't have to define every member -- serde will ignore extra
10-
/// data when deserializing
11-
#[derive(Debug, Serialize, Deserialize)]
12-
pub struct Branch {
13-
pub name: String,
14-
pub commit: Commit,
15-
}
16-
17-
#[derive(Debug, Serialize, Deserialize)]
18-
pub struct Commit {
19-
pub sha: String,
20-
pub commit: CommitDetails,
21-
}
22-
23-
#[derive(Debug, Serialize, Deserialize)]
24-
pub struct CommitDetails {
25-
pub author: Signature,
26-
pub committer: Signature,
27-
}
28-
29-
#[derive(Debug, Serialize, Deserialize)]
30-
pub struct Signature {
31-
pub name: String,
32-
pub email: String,
33-
}
34-
356
#[wasm_bindgen]
367
pub async fn run(repo: String) -> Result<JsValue, JsValue> {
378
let mut opts = RequestInit::new();
@@ -56,9 +27,6 @@ pub async fn run(repo: String) -> Result<JsValue, JsValue> {
5627
// Convert this other `Promise` into a rust `Future`.
5728
let json = JsFuture::from(resp.json()?).await?;
5829

59-
// Use serde to parse the JSON into a struct.
60-
let branch_info: Branch = json.into_serde().unwrap();
61-
62-
// Send the `Branch` struct back to JS as an `Object`.
63-
Ok(JsValue::from_serde(&branch_info).unwrap())
30+
// Send the JSON response back to JS.
31+
Ok(json)
6432
}

examples/raytrace-parallel/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ js-sys = "0.3.59"
1313
rayon = "1.1.0"
1414
rayon-core = "1.5.0"
1515
raytracer = { git = 'https://github.com/alexcrichton/raytracer', branch = 'update-deps' }
16+
serde-wasm-bindgen = "0.4.3"
1617
futures-channel-preview = "0.3.0-alpha.18"
17-
wasm-bindgen = { version = "0.2.82", features = ['serde-serialize'] }
18+
wasm-bindgen = "0.2.82"
1819
wasm-bindgen-futures = "0.4.32"
1920

2021
[dependencies.web-sys]

examples/raytrace-parallel/src/lib.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,10 @@ impl Scene {
2828
/// Creates a new scene from the JSON description in `object`, which we
2929
/// deserialize here into an actual scene.
3030
#[wasm_bindgen(constructor)]
31-
pub fn new(object: &JsValue) -> Result<Scene, JsValue> {
31+
pub fn new(object: JsValue) -> Result<Scene, JsValue> {
3232
console_error_panic_hook::set_once();
3333
Ok(Scene {
34-
inner: object
35-
.into_serde()
34+
inner: serde_wasm_bindgen::from_value(object)
3635
.map_err(|e| JsValue::from(e.to_string()))?,
3736
})
3837
}

examples/webxr/Cargo.toml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,8 @@ crate-type = ["cdylib"]
1010
[dependencies]
1111
futures = "0.3.4"
1212
js-sys = "0.3.59"
13-
wasm-bindgen = {version = "0.2.82", features = ["serde-serialize"]}
13+
wasm-bindgen = "0.2.82"
1414
wasm-bindgen-futures = "0.4.32"
15-
serde = { version = "1.0.80", features = ["derive"] }
16-
serde_derive = "^1.0.59"
1715

1816
[dependencies.web-sys]
1917
version = "0.3.36"

examples/webxr/src/lib.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,13 @@
33
#[macro_use]
44
mod utils;
55

6-
use futures::{future, Future};
7-
use js_sys::Promise;
6+
use js_sys::{Object, Promise, Reflect};
87
use std::cell::RefCell;
9-
use std::collections::HashMap;
108
use std::rc::Rc;
119
use utils::set_panic_hook;
1210
use wasm_bindgen::prelude::*;
1311
use wasm_bindgen::JsCast;
1412
use wasm_bindgen_futures::future_to_promise;
15-
use wasm_bindgen_futures::JsFuture;
1613
use web_sys::*;
1714

1815
// 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
3936
.unwrap();
4037

4138
let gl: WebGl2RenderingContext = if xr_mode {
42-
let mut gl_attribs = HashMap::new();
43-
gl_attribs.insert(String::from("xrCompatible"), true);
44-
let js_gl_attribs = JsValue::from_serde(&gl_attribs).unwrap();
39+
let gl_attribs = Object::new();
40+
Reflect::set(
41+
&gl_attribs,
42+
&JsValue::from_str("xrCompatible"),
43+
&JsValue::TRUE,
44+
)
45+
.unwrap();
4546

4647
canvas
47-
.get_context_with_context_options("webgl2", &js_gl_attribs)?
48+
.get_context_with_context_options("webgl2", &gl_attribs)?
4849
.unwrap()
4950
.dyn_into()?
5051
} else {
@@ -77,7 +78,6 @@ impl XrApp {
7778
pub fn init(&self) -> Promise {
7879
log!("Starting WebXR...");
7980
let navigator: web_sys::Navigator = web_sys::window().unwrap().navigator();
80-
let gpu = navigator.gpu();
8181
let xr = navigator.xr();
8282
let session_mode = XrSessionMode::Inline;
8383
let session_supported_promise = xr.is_session_supported(session_mode);
@@ -121,7 +121,7 @@ impl XrApp {
121121
let g = f.clone();
122122

123123
let mut i = 0;
124-
*g.borrow_mut() = Some(Closure::new(move |time: f64, frame: XrFrame| {
124+
*g.borrow_mut() = Some(Closure::new(move |_time: f64, frame: XrFrame| {
125125
log!("Frame rendering...");
126126
if i > 2 {
127127
log!("All done!");

guide/src/examples/fetch.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ then parses the resulting JSON.
1111
## `Cargo.toml`
1212

1313
The `Cargo.toml` enables a number of features related to the `fetch` API and
14-
types used: `Headers`, `Request`, etc. It also enables `wasm-bindgen`'s `serde`
15-
support.
14+
types used: `Headers`, `Request`, etc.
1615

1716
```toml
1817
{{#include ../../../examples/fetch/Cargo.toml}}
Lines changed: 73 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
11
# Serializing and Deserializing Arbitrary Data Into and From `JsValue` with Serde
22

33
It's possible to pass arbitrary data from Rust to JavaScript by serializing it
4-
to JSON with [Serde](https://github.com/serde-rs/serde). `wasm-bindgen` includes
5-
the `JsValue` type, which streamlines serializing and deserializing.
4+
with [Serde](https://github.com/serde-rs/serde). This can be done through the
5+
[`serde-wasm-bindgen`](https://docs.rs/serde-wasm-bindgen) crate.
66

7-
## Enable the `"serde-serialize"` Feature
7+
## Add dependencies
88

9-
To enable the `"serde-serialize"` feature, do two things in `Cargo.toml`:
10-
11-
1. Add the `serde` and `serde_derive` crates to `[dependencies]`.
12-
2. Add `features = ["serde-serialize"]` to the existing `wasm-bindgen`
13-
dependency.
9+
To use `serde-wasm-bindgen`, you first have to add it as a dependency in your
10+
`Cargo.toml`. You also need the `serde` crate, with the `derive` feature
11+
enabled, to allow your types to be serialized and deserialized with Serde.
1412

1513
```toml
1614
[dependencies]
1715
serde = { version = "1.0", features = ["derive"] }
18-
wasm-bindgen = { version = "0.2", features = ["serde-serialize"] }
16+
serde-wasm-bindgen = "0.4"
1917
```
2018

2119
## Derive the `Serialize` and `Deserialize` Traits
@@ -42,7 +40,7 @@ pub struct Example {
4240
}
4341
```
4442

45-
## Send it to JavaScript with `JsValue::from_serde`
43+
## Send it to JavaScript with `serde_wasm_bindgen::to_value`
4644

4745
Here's a function that will pass an `Example` to JavaScript by serializing it to
4846
`JsValue`:
@@ -58,28 +56,28 @@ pub fn send_example_to_js() -> JsValue {
5856
field3: [1., 2., 3., 4.]
5957
};
6058

61-
JsValue::from_serde(&example).unwrap()
59+
serde_wasm_bindgen::to_value(&example).unwrap()
6260
}
6361
```
6462

65-
## Receive it from JavaScript with `JsValue::into_serde`
63+
## Receive it from JavaScript with `serde_wasm_bindgen::from_value`
6664

6765
Here's a function that will receive a `JsValue` parameter from JavaScript and
6866
then deserialize an `Example` from it:
6967

7068
```rust
7169
#[wasm_bindgen]
72-
pub fn receive_example_from_js(val: &JsValue) {
73-
let example: Example = val.into_serde().unwrap();
70+
pub fn receive_example_from_js(val: JsValue) {
71+
let example: Example = serde_wasm_bindgen::from_value(val).unwrap();
7472
...
7573
}
7674
```
7775

7876
## JavaScript Usage
7977

80-
In the `JsValue` that JavaScript gets, `field1` will be an `Object` (not a
81-
JavaScript `Map`), `field2` will be a JavaScript `Array` whose members are
82-
`Array`s of numbers, and `field3` will be an `Array` of numbers.
78+
In the `JsValue` that JavaScript gets, `field1` will be a `Map`, `field2` will
79+
be a JavaScript `Array` whose members are `Array`s of numbers, and `field3`
80+
will be an `Array` of numbers.
8381

8482
```js
8583
import { send_example_to_js, receive_example_from_js } from "example";
@@ -94,23 +92,61 @@ example.field2.push([5, 6]);
9492
receive_example_from_js(example);
9593
```
9694

97-
## An Alternative Approach: `serde-wasm-bindgen`
98-
99-
[The `serde-wasm-bindgen`
100-
crate](https://github.com/cloudflare/serde-wasm-bindgen) serializes and
101-
deserializes Rust structures directly to `JsValue`s, without going through
102-
temporary JSON stringification. This approach has both advantages and
103-
disadvantages.
104-
105-
The primary advantage is smaller code size: going through JSON entrenches code
106-
to stringify and parse floating point numbers, which is not a small amount of
107-
code. It also supports more types than JSON does, such as `Map`, `Set`, and
108-
array buffers.
109-
110-
There are two primary disadvantages. The first is that it is not always
111-
compatible with the default JSON-based serialization. The second is that it
112-
performs more calls back and forth between JS and Wasm, which has not been fully
113-
optimized in all engines, meaning it can sometimes be a speed
114-
regression. However, in other cases, it is a speed up over the JSON-based
115-
stringification, so &mdash; as always &mdash; make sure to profile your own use
116-
cases as necessary.
95+
## An alternative approach - using JSON
96+
97+
`serde-wasm-bindgen` works by directly manipulating JavaScript values. This
98+
requires a lot of calls back and forth between Rust and JavaScript, which can
99+
sometimes be slow. An alternative way of doing this is to serialize values to
100+
JSON, and then parse them on the other end. Browsers' JSON implementations are
101+
usually quite fast, and so this approach can outstrip `serde-wasm-bindgen`'s
102+
performance in some cases.
103+
104+
That's not to say that using JSON is always faster, though - the JSON approach
105+
can be anywhere from 2x to 0.2x the speed of `serde-wasm-bindgen`, depending on
106+
the JS runtime and the values being passed. It also leads to larger code size
107+
than `serde-wasm-bindgen`. So, make sure to profile each for your own use
108+
cases.
109+
110+
This approach is implemented in [`gloo_utils::format::JsValueSerdeExt`]:
111+
112+
```toml
113+
# Cargo.toml
114+
[dependencies]
115+
gloo-utils = { version = "0.1", features = ["serde"] }
116+
```
117+
118+
```rust
119+
use gloo_utils::format::JsValueSerdeExt;
120+
121+
#[wasm_bindgen]
122+
pub fn send_example_to_js() -> JsValue {
123+
let mut field1 = HashMap::new();
124+
field1.insert(0, String::from("ex"));
125+
let example = Example {
126+
field1,
127+
field2: vec![vec![1., 2.], vec![3., 4.]],
128+
field3: [1., 2., 3., 4.]
129+
};
130+
131+
JsValue::from_serde(&example).unwrap()
132+
}
133+
134+
#[wasm_bindgen]
135+
pub fn receive_example_from_js(val: JsValue) {
136+
let example: Example = val.into_serde().unwrap();
137+
...
138+
}
139+
```
140+
141+
[`gloo_utils::format::JsValueSerdeExt`]: https://docs.rs/gloo-utils/latest/gloo_utils/format/trait.JsValueSerdeExt.html
142+
143+
## History
144+
145+
In previous versions of `wasm-bindgen`, `gloo-utils`'s JSON-based Serde support
146+
(`JsValue::from_serde` and `JsValue::into_serde`) was built into `wasm-bindgen`
147+
itself. However, this required a dependency on `serde_json`, which had a
148+
problem: with certain features of `serde_json` and other crates enabled,
149+
`serde_json` would end up with a circular dependency on `wasm-bindgen`, which
150+
is illegal in Rust and caused people's code to fail to compile. So, these
151+
methods were extracted out into `gloo-utils` with an extension trait and the
152+
originals were deprecated.

src/lib.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,14 @@ impl JsValue {
201201
/// Creates a new `JsValue` from the JSON serialization of the object `t`
202202
/// provided.
203203
///
204+
/// **This function is deprecated**, due to [creating a dependency cycle in
205+
/// some circumstances][dep-cycle-issue]. Use [`serde-wasm-bindgen`] or
206+
/// [`gloo_utils::format::JsValueSerdeExt`] instead.
207+
///
208+
/// [dep-cycle-issue]: https://github.com/rustwasm/wasm-bindgen/issues/2770
209+
/// [`serde-wasm-bindgen`]: https://docs.rs/serde-wasm-bindgen
210+
/// [`gloo_utils::format::JsValueSerdeExt`]: https://docs.rs/gloo-utils/latest/gloo_utils/format/trait.JsValueSerdeExt.html
211+
///
204212
/// This function will serialize the provided value `t` to a JSON string,
205213
/// send the JSON string to JS, parse it into a JS object, and then return
206214
/// a handle to the JS object. This is unlikely to be super speedy so it's
@@ -214,6 +222,7 @@ impl JsValue {
214222
///
215223
/// Returns any error encountered when serializing `T` into JSON.
216224
#[cfg(feature = "serde-serialize")]
225+
#[deprecated = "causes dependency cycles, use `serde-wasm-bindgen` or `gloo_utils::format::JsValueSerdeExt` instead"]
217226
pub fn from_serde<T>(t: &T) -> serde_json::Result<JsValue>
218227
where
219228
T: serde::ser::Serialize + ?Sized,
@@ -225,6 +234,14 @@ impl JsValue {
225234
/// Invokes `JSON.stringify` on this value and then parses the resulting
226235
/// JSON into an arbitrary Rust value.
227236
///
237+
/// **This function is deprecated**, due to [creating a dependency cycle in
238+
/// some circumstances][dep-cycle-issue]. Use [`serde-wasm-bindgen`] or
239+
/// [`gloo_utils::format::JsValueSerdeExt`] instead.
240+
///
241+
/// [dep-cycle-issue]: https://github.com/rustwasm/wasm-bindgen/issues/2770
242+
/// [`serde-wasm-bindgen`]: https://docs.rs/serde-wasm-bindgen
243+
/// [`gloo_utils::format::JsValueSerdeExt`]: https://docs.rs/gloo-utils/latest/gloo_utils/format/trait.JsValueSerdeExt.html
244+
///
228245
/// This function will first call `JSON.stringify` on the `JsValue` itself.
229246
/// The resulting string is then passed into Rust which then parses it as
230247
/// JSON into the resulting value.
@@ -236,6 +253,7 @@ impl JsValue {
236253
///
237254
/// Returns any error encountered when parsing the JSON into a `T`.
238255
#[cfg(feature = "serde-serialize")]
256+
#[deprecated = "causes dependency cycles, use `serde-wasm-bindgen` or `gloo_utils::format::JsValueSerdeExt` instead"]
239257
pub fn into_serde<T>(&self) -> serde_json::Result<T>
240258
where
241259
T: for<'a> serde::de::Deserialize<'a>,

0 commit comments

Comments
 (0)