Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert collection of hash maps to a csv file

Tags:

clojure

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.

like image 729
redhands Avatar asked Sep 02 '13 11:09

redhands


2 Answers

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
like image 81
Bobby Norton Avatar answered Nov 18 '22 05:11

Bobby Norton


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!

like image 3
leontalbot Avatar answered Nov 18 '22 04:11

leontalbot