Logging reason values


#1

A pain point that I keep running into is naively passing a reason value in Js.log() and getting somewhat mangled output.

For any particular example, I’m able to convert it to something more js-friendly (for example, convert a list to an array, or convert a record to a Json.t or even a Js.Dict.t(string)), so I’m not looking for help on specific examples. I am, however, wondering if there are conventions or ways to make this easier.

In case it helps direct the discussion, here are two possible ways this could become easier:

  1. Pure convention, but every time you define a new type, have the standard practice be to define a let toJson : t => Json.t function. Then, people would log via Js.log(M.toJson(x)). It would be especially important for library writers to expose this for all the types in their API.

  2. To make the above convention much easier, have the ability to derive a JSON representation of records/variants. For this, I’m thinking something like Jane Street’s [@@deriving sexp] that would recursively call toJson on record fields or variant payloads.

Thoughts?


#2

If I’m not mistaken, the two biggest surprises for logging data are records and lists, which get logged as weird-looking arrays. For records, we have a helper to define a conversion to a JavaScript object: https://bucklescript.github.io/docs/en/generate-converters-accessors.html#convert-between-jst-object-and-record –only problem is it only works at a single level of nesting, so it can’t convert nested record values. For nested records, you’ll probably want to define your own converter function. I agree it would be great to have autogen converters for arbitrary data. I think this could tie in with having e.g. Swagger codegen support.

We also have Array.of_list to convert a list to an array, which will be output as a normal JavaScript array.


Using Json in Reason with Belt
#3

Oh, amazing! So the deriving solution already exists, thanks.

I do think applying it recursively will become more and more important as the dependency graph of reason libraries becomes more complicated.

One minor usage question. If I want to expose the fact that my custom type derives tToJs, it looks like the value I’d expose (in my particular example) looks something like this:

t =>
{. "color": Color.t, "flags": string, "from": Square.t,
  "piece": Piece.Type.t, "san": san, "to_": Square.t}

which is a bit more than I’d like to expose. Is there a way to say something more similar to let tToJs : t => Js.t(_)?


#4

As far as I know, no; you could constrain it to let toToJs: t => Js.t('a), but you’d likely run into type errors.

If you want to expose only an abstract structure that will be printed in the right way, your best bet is probably to provide a converter to Js.Json.t.


#5

Got it, thanks. I agree => Js.Json.t is probably closer to what I’m looking for. It feels like that would be much more composable too, for a recursive “deriving Json” implementation.


#6

For the case of logging lists, I find myself doing the array conversion every time for readability. Two ways to do this without excessive parens:
Js.log @@ Array.of_list @@ myValue
or
Js.log(myValue |> Array.of_list)


#7

myValue |> Array.of_list |> Js.log will also work.


How do you convert a record to a js object?