I have the following collection of hash maps:
{a:"Completed" b:1 c:"Friday" d:4}
{a:"Started" b:1 c:"Monday" d:4}
{a:"In Progress" b:1 c:"Sunday" d:1}
{a:"Completed" b:3 c:"Tuesday" d:9}
How can I convert this to a CSV file in clojure?
i.e.
a,b,c,d
Completed,1,Friday,4
Started,1,Monday,4
In Progress,1,Sunday,1
Completed,3,Tuesday,9
Any help with this would be much appreciated.
You didn't explicitly say you were starting with JSON, but let's assume you are.
I would use cheshire
to parse your JSON string to get, in this case, a vector of maps
representing your data. We'll assume you've done that for sake
of simplicity and just use a var
called data
to avoid
cluttering the example.
Now we can create a vector of vectors, then use clojure.data.csv to save the results to a file.
You can try this out at the REPL using lein try.
Follow the setup instructions for lein try
if you don't already have it, then run
lein try clojure.data.csv 0.1.2
. Once you're in a REPL with this dependency:
(require '[clojure.data.csv :as csv] '[clojure.java.io :as io])
(def data
[{:a "Completed" :b 1 :c "Friday" :d 4}
{:a "Started" :b 1 :c "Monday" :d 4}
{:a "In Progress" :b 1 :c "Sunday" :d 1}
{:a "Completed" :b 3 :c "Tuesday" :d 9}])
(defn write-csv [path row-data]
(let [columns [:a :b :c :d]
headers (map name columns)
rows (mapv #(mapv % columns) row-data)]
(with-open [file (io/writer path)]
(csv/write-csv file (cons headers rows)))))
(write-csv "/tmp/results.csv" data)
Now you can see the results of your handiwork:
$ cat /tmp/results.csv
a,b,c,d
Completed,1,Friday,4
Started,1,Monday,4
In Progress,1,Sunday,1
Completed,3,Tuesday,9
I read all the answers but I wanted more generic and reusable solution.
Here is what I came up with. You can use write-csv
and maps->csv-data
independently or together via write-csv-from-maps
. More importantly, you don't need to hard code headers. Just pass your vector of maps and you should be all set.
A. DIY Approach
(require '[clojure.data.csv :as csv]
'[clojure.java.io :as io])
(defn write-csv
"Takes a file (path, name and extension) and
csv-data (vector of vectors with all values) and
writes csv file."
[file csv-data]
(with-open [writer (io/writer file)]
(csv/write-csv writer csv-data)))
(defn maps->csv-data
"Takes a collection of maps and returns csv-data
(vector of vectors with all values)."
[maps]
(let [columns (-> maps first keys)
headers (mapv name columns)
rows (mapv #(mapv % columns) maps)]
(into [headers] rows)))
(defn write-csv-from-maps
"Takes a file (path, name and extension) and a collection of maps
transforms data (vector of vectors with all values)
writes csv file."
[file maps]
(->> maps maps->csv-data (write-csv file)))
B. Semantic CSV Approach
Alternatively, you can use semantic-csv
library.
maps->csv-data
would then become semantic-csv.core/vectorize
(require '[clojure.data.csv :as csv]
'[clojure.java.io :as io]
'[semantic-csv.core :as sc])
(defn write-csv
"Takes a file (path, name and extension) and
csv-data (vector of vectors with all values) and
writes csv file."
[file csv-data]
(with-open [writer (io/writer file)]
(csv/write-csv writer csv-data)))
(defn write-csv-from-maps
"Takes a file (path, name and extension) and a collection of maps
transforms data (vector of vectors with all values)
writes csv file."
[file maps]
(->> maps sc/vectorize (write-csv file)))
--
Finally:
(def data
[{:a "Completed" :b 1 :c "Friday" :d 4}
{:a "Started" :b 1 :c "Monday" :d 4}
{:a "In Progress" :b 1 :c "Sunday" :d 1}
{:a "Completed" :b 3 :c "Tuesday" :d 9}])
(write-csv "/tmp/results.csv" data)
Hope that could be useful to someone!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With