How to generate CSV in ClojureScript?

In other apps we generate the CSV on the backend using clojure/data.csv and then send that data back to the front-end; to make this a smooth part of our SPA, we then embed it into a “download now” button using a data-url string, the result being that you get an on-demand csv download without ever leaving the app. This works out okay. However, in our current app we need to be able to edit our data view (front-end) and then download the CSV that goes with this edited view. We COULD just ship the data to the backend, have it do its csv conversion, and send it back to the front-end, but really there doesn’t seem to be any reason we don’t just do the whole operation on the front-end (ClojureScript). But before we go adapting our own library out of data.csv, I promised to ask all of you whether you have solutions you’ve used for writing CSVs from flat (non-nested) data structures of the same sort you get from HoneySQL. That is, vectors of maps, where each map is one row of the CSV.

Other libraries we’ve tried fail on maps, or make some awkward assumptions about the data schema.

Should we just write our own (working through the several gotchas involved in CSV format), or is there already a working solution out there?

I’d just use some NPM library for it.

1 Like

@Webdev_Tory, I have the exact same question. Please post if you find a good front-end csv generator. :blush:

I do use GitHub - testdouble/clojurescript.csv: A ClojureScript library for reading and writing CSV, but it probably falls under the category of libraries that fail on maps, as you mention. I am wondering - are you worried about the overhead of transforming your vector of maps to e.g. a vector of vectors that could work with an existing library?

@Webdev_Tory - If it’s purely for writing data, join works terrific.

(def DATA [{:name "John" :age 10 :likes "Ice Cream"}                                                                                            
           {:name "Billy" :age 9 :likes "Dinosaurs"}])

(clojure.string/join ", " (map (first DATA) [:name :age :likes]))
=> "John, 10, Ice Cream"

(clojure.string/join ", " (map (second DATA) [:name :age :likes]))
=> "Billy, 9, Dinosaurs"
     
2 Likes

This will fail when the cell values contain commas , or quotes ". Relevant section of a spec for CSV:

   4.  Within the header and each record, there may be one or more
       fields, separated by commas.  Each line should contain the same
       number of fields throughout the file.  Spaces are considered part
       of a field and should not be ignored.  The last field in the
       record must not be followed by a comma.  For example:

       aaa,bbb,ccc

   5.  Each field may or may not be enclosed in double quotes (however
       some programs, such as Microsoft Excel, do not use double quotes
       at all).  If fields are not enclosed with double quotes, then
       double quotes may not appear inside the fields.  For example:

       "aaa","bbb","ccc" CRLF
       zzz,yyy,xxx

   6.  Fields containing line breaks (CRLF), double quotes, and commas
       should be enclosed in double-quotes.  For example:

       "aaa","b CRLF
       bb","ccc" CRLF
       zzz,yyy,xxx

   7.  If double-quotes are used to enclose fields, then a double-quote
       appearing inside a field must be escaped by preceding it with
       another double quote.  For example:

       "aaa","b""bb","ccc"
4 Likes

Ah! You replied before I could, with the same "gotcha"s I was going to mention but with a great link. Thanks! Yes, the text-field bits of the CSV spec makes life harder than just a join.