Binding csv-stringify library

reasonreact
interop

#1

Hello, I’m still not familiar with ReasonML and Bucklescript. I want to bind csv-stringify library.

In Javascript:
const stringify = require(“csv-stringify”);
const fs = require(“fs”);

let data = [];
let columns = {
id: “id”,
name: “Name”
};

for (var i = 0; i < 10; i++) {
data.push([i, "Name " + i]);
}

stringify(data, { header: true, columns: columns }, (err, output) => {
if (!err) {
fs.writeFile(“my.csv”, output, err => {
if (!err) {
console.log(“ok”);
}
});
}
});

I tried to bind it like this:
[@bs.module “csv-stringify”]
external stringify: 'a => unit = “stringify”;

But it’s throwing error like this:
TypeError: CsvStringify.stringify is not a function
at Object. (/Users/silverbullet/Documents/Projects/Contracts/Landslides/Project/reason-csv-writer/src/Index.bs.js:20:14)
at Module._compile (internal/modules/cjs/loader.js:701:30)
at Object.Module._extensions…js (internal/modules/cjs/loader.js:712:10)
at Module.load (internal/modules/cjs/loader.js:600:32)
at tryModuleLoad (internal/modules/cjs/loader.js:539:12)
at Function.Module._load (internal/modules/cjs/loader.js:531:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:754:12)
at startup (internal/bootstrap/node.js:283:19)
at bootstrapNodeJSCore (internal/bootstrap/node.js:622:3)

Would you like to help me with binding this?


#2

Sorry, I should answer my own question.

type dataType = Js.Array.t((int, string));
type error;

[@bs.module]
external stringify: (dataType, (Js.nullable(error), string) => unit) => unit =
“csv-stringify”;

stringify(data, (_, output) => Js.log(output));

But I still having problem if I try to use the options in stringify function like
stringify(data, { header: true, columns: columns }, (err, output) => {});

Please help me how to handle this.


#3

I guess you want to bind to the simpler callback API: https://csv.js.org/stringify/api/#callback-api

It’s slightly tricky to bind to this API because it can accept CSV data of different numbers of columns, represented as tuples of different sizes. The input data needs to be tuples, as you know, because tuples can represent heterogeneous data types whereas arrays in Reason can only contain a single data type in the array. Since JavaScript doesn’t have this restriction (types are checked at runtime anyway), it doesn’t have this trickiness up front.

So with that in mind, we can model this function in Reason by binding several different bindings for it, each one to represent columns of different sizes. This is a fairly common practice to deal with dynamic JS arrays like this. See e.g. the ReasonReact bindings for the useEffect hook.

Here’s the binding for two columns:

// CsvStringify.re

// These are common to all column sizes:

type options;

[@bs.obj] external options: (
  ~headers: bool=?,
  ~columns: Js.t({..})=?,
  unit,
) => options = "";

// This is the binding for 2-column CSVs:

[@bs.module]
external stringify2: (
  array(('a, 'b)),
  options,
  (. option(Js.Promise.error), option(string)) => unit,
) => unit = "csv-stringify";

// This is a convenience function to wrap the binding:

let stringify2(
  ~headers=false,
  ~columns=Js.Obj.empty(),
  data,
  callback,
) = stringify2(
  data,
  options(~headers, ~columns, ()),
  callback,
);

You can call it like this:

stringify2(
  ~headers=true,
  ~columns={"id": "id", "name": "Name"},
  [|
    (1, "Bob"),
    (2, "Jim"),
  |],
  (. err, result) => Js.log(result),
);

You can easily write more bindings for stringify3, stringify4, etc.


#4

Thank you. Honestly, I solved it a bit different way, but this seems much better and detailed. Thanks again for your help.