Say I have the following ReasonML types:
type xEntry = {title: string};
type yEntry = {value: int};
type entry =
| X(xEntry)
| Y(yEntry);
and I want to export the following value to the JavaScript side:
/* Input */
let value = [
X({title: "foo"}),
Y({value: 123})
];
as the following structure:
/* Expected output */
[{"type": "X", "title": "foo"},
{"type": "Y", "value": 123}]
I can almost achieve it with the following code:
[@bs.deriving abstract]
type jsXEntry = {
[@bs.as "type"]
type_: string,
title: string,
};
[@bs.deriving abstract]
type jsYEntry = {
[@bs.as "type"]
type_: string,
value: int,
};
type jsEntry =
| JsX(jsXEntry)
| JsY(jsYEntry);
let fromEntry = entry =>
switch (entry) {
| X(v) => JsX(jsXEntry(~type_="X", ~title=v.title))
| Y(v) => JsY(jsYEntry(~type_="Y", ~value=v.value))
};
let convertToJs = (entries: list(entry)): Js.Array.t(jsEntry) =>
Array.map(fromEntry, ArrayLabels.of_list(entries));
Unfortunately (but understandably) I get the following result:
/* convertToJs(value) */
[ [ { type: 'X', title: 'foo' }, tag: 0 ],
[ { type: 'Y', value: 123 }, tag: 1 ] ]
This result contains tagged records (a single-element array, with a tag
attribute), which is not what I'm looking for.
I could write a conversion function on the JavaScript side, to get rid of that, but I would prefer to directly produce the right structure from ReasonML.
Also, I would like to avoid using %bs.raw
construct if possible.
How can I produce the expected output shown at the top?
You could use an abstract type instead of a variant for jsEntry
, then use Obj.magic
to circumvent the type system and cast it to that type:
type jsEntry;
let fromEntry: entry => jsEntry =
fun | X(v) => Obj.magic(jsXEntry(~type_="X", ~title=v.title))
| Y(v) => Obj.magic(jsYEntry(~type_="Y", ~value=v.value))
;
Be careful with Obj.magic
though, since circumventing the type system can easily break your code. Make sure to always annotate the types to keep type inference from giving you surprises.