Skip to content

Parsing framework for javascript objects (serde without the string step) #69

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

Closed
richard-uk1 opened this issue Apr 6, 2019 · 5 comments

Comments

@richard-uk1
Copy link
Contributor

Summary

Provide a serialization and deserialization framework for converting between javascript values (wasm_bindgen::JsValue) and rust values.

Related proposals:

Motivation

Currently, there are a few different ways to work with javascript values, but they are all sub-optimal.

Case 1: extern type & JsCast

I guess this is the low-level way of working with javascript objects. We don't use reflection like js_sys::Object or js_sys::Reflect. If we use dyn_into, we will get an instanceof check, but there is no check that the values we define in the #[wasm_bindgen] extern block actually match the properties the javascript object contains.

Case 2: js_sys::Object and js_sys::Reflect

These types provide reflection - we can check to see if an object has a property or not. This is a good pragmatic safe way of providing a checked gateway into the typesafe rust land. If we check for all parameters we expect, there is probably some overhead, but we know the values we create in rust are correct and we can avoid panics. TODO I need to understand better what happens if you import a non-existant property in a wasm-bindgen extern block.

Case 3: JsValue::{from/into}_serde

In this case we just convert our javascript type into a string and then re-parse it using the awesome serde framework. This is a reliable way to check we get exactly what we want, but has the following downsides:

  1. It's slow. We have to encode, and then parse needlessly
  2. We can only work with javascript types that work with JSON.stringify. For example, we can't hold javascript functions, and we lose type information converting objects into json.
  3. (related to above) sometimes we want to partially parse a javascript value: for example, we may want to convert an object into rust, but then leave all the properties as opaque js objects. We can't do this here - we have to convert everything.

This proposal aims to provide fine-grained control of parsing exactly what you want to, leaving opaque what you want to, and doing it all without going through a string.

Detailed Explanation

What follows is just my early thoughts on how this might work. It needs iteration.

The serde framework provides a great template for how to generate serialization and deserialization code. I would envisage this working like

#[derive(GlooDeserialize)]
pub struct SomeJsValue {
    // a plain javascript datatype
    my_int: i32,
    // in javascript this would be `value.point == {x: 1, y: 1}`
    // unsure whether to look at Js prototype name or just properties
    point: Subtype,
    // a javascript function that we don't work with - we just pass it back into JS code
    my_func: js_sys::Function,
    // something we don't want to parse, we just check it's there
    arbitrary_value: wasm_bindgen::JsValue,
    // maybe we can distinguish between a property being null, and it not being defined
    #[glooserde(null)]
    should_be_null: (),
}

#[derive(GlooDeserialize)]
pub struct Subtype {
    x: i32,
    y: i32
}

There is some complexity from the fact that we do different things for different types, and special case types from wasm_bindgen/js_sys. For a JsValue, we just have to check the object has a property and then get a handle on the property, for functions, we would check in js that the object is a function, and then for actual objects we use reflection to check the properties are there and then copy them (or a handle for complex objects) into rust.

Drawbacks, Rationale, and Alternatives

The main drawback of this is it's probably a lot of work to implement.

We also already have all the options above, so we could use them, or develop something like gloo-properties. We could possibly build this on top of something like gloo-properties - maybe that would decrease complexity.

Prior art

  • serde
  • how elm parses javascript values - Not sure if this applies directly to this conversation, but anything Evan writes tends to be extremely good. elm goes through JSON like serde.

Unresolved Questions

Basically everything about this. Is it a good idea? How would we implement it? I feel its quite ambitious & complex.

@fitzgen
Copy link
Member

fitzgen commented Apr 8, 2019

This seems very similar to what we have been suggesting / considering for wasm-bindgen's default serde behavior: rustwasm/wasm-bindgen#1258

cc @RReverser, who actually investigated this further and had some interesting performance results: rustwasm/wasm-bindgen#1258 (comment)

Unless we end up with something that is fundamentally incompatible with wasm-bindgen's existing serde integration, I'd rather just improve that and let the whole downstream ecosystem reap the benefits.

@richard-uk1
Copy link
Contributor Author

Using serde would be preferable :).

@RReverser
Copy link
Member

Unless we end up with something that is fundamentally incompatible with wasm-bindgen's existing serde integration, I'd rather just improve that and let the whole downstream ecosystem reap the benefits.

In the course of this work and one of the discussions in #53 I actually started thinking of a vague proposal for object passing that could be still useful with or without serde integration, but it's not fully formed to write down yet...

@richard-uk1
Copy link
Contributor Author

This now already exists, with a wasm-bindgen note: https://github.com/rustwasm/wasm-bindgen/pull/1553/files.

@RReverser
Copy link
Member

@derekdreery Oh yeah, I built it but didn't reference here. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants