js_of_ocaml icon indicating copy to clipboard operation
js_of_ocaml copied to clipboard

Add option to not include undefined values in resulting object literal

Open jchavarri opened this issue 6 years ago • 7 comments

Using

let obj = object%js (self)
  val z = Js.undefined [@@jsoo.optdef]
end

It still includes {z: undefined} in the resulting object literal. It'd be helpful to have an option (or a new attribute) to just not include the property altogether if it's undefined.

BuckleScript has something similar with bs.obj (example).

cc @Drup

jchavarri avatar May 13 '19 07:05 jchavarri

What would you expect the following to do ?

let f x =
  object%js (self)
    val z = x [@@jsoo.optdef]
  end

hhugo avatar May 13 '19 08:05 hhugo

@hhugo I don't think the current behavior of [@@jsoo.optdef] is totally unexpected, but I was hoping to have a way for the property to not be included in the resulting literal if the value is undefined.

So in the snippet you shared, calling f Js.undefined would return an empty object {}.

jchavarri avatar May 13 '19 09:05 jchavarri

In case it helps, this is the part of the BuckleScript codebase where the external ... = "" [@@bs.obj] case is handled.

Here's the documentation for it.

As you can see, it walks through the args of the external declaration, and then calls Ffi_obj_create. The functionality provided by external + [@@bs.obj] has been extremely helpful in my experience, as it allows to create these literals with optional properties in a very ergonomic way using function labels.

I know this kind of design with annotations for externals is not common in jsoo... but I thought it'd be worth mentioning for more context.

jchavarri avatar May 13 '19 10:05 jchavarri

I'm not against the idea. What do you expect the generated javascript to be. Creating an empty object and and have a bunch of if-then to set fields if not undefined?

hhugo avatar May 13 '19 11:05 hhugo

This is a sample implementation I did using the existing APIs:

let optInj prop opt =
  match opt with
  | Some (s) -> [|(prop, Js.Unsafe.inject s)|]
  | None  -> [||]

let create ?x:(x : string option)  ?y:(y : int option)  ?z:(z : int option) () =
  optInj "x" x
  |> Array.append (optInj "y" y)
  |> Array.append (optInj "z" z)

let obj = Js.Unsafe.obj (create ~x:"2" ~y:2 ()) (* {x: "2", y: 2} *)
let obj2 = Js.Unsafe.obj (create ()) (* {} *)

jchavarri avatar May 13 '19 11:05 jchavarri

What do you expect the generated javascript to be. Creating an empty object and and have a bunch of if-then to set fields if not undefined?

@hhugo Yes, in the same vein as this.

As a side question, I'm not sure how BuckleScript does it but it inlines most of the usages. I guess this is an optimization that already exists in the OCaml compiler, right?

jchavarri avatar May 13 '19 12:05 jchavarri

Was there any action taken as a result of this question? I would find that capability quite useful.

xguerin avatar Nov 06 '20 09:11 xguerin