15

I need to save clojure maps to a file and read them back later to process them.

This is what I could come up with. Is there a better way to accomplish the same thing?

user=> (def my-data (for [ a [ "Person1" "Person2" ]  b [ "Address1" "Address2"]  c   (range 10) ] {:name a :address b :index c} ))
#'user/my-data
user=> (count my-data)
40

user=> (defn write-data[xs] (with-open [wrtr (clojure.java.io/writer "my-data.txt") ]
              (doall (map #(.write wrtr (str % "\n")) xs))))
#'user/write-data

user=> (write-data my-data)

user=> (defn read-data[] (with-open [rdr (clojure.java.io/reader "my-data.txt") ]
            (doall (map #(load-string %) (line-seq rdr)))))
#'user/read-data

user=> (count (read-data))
40
tshepang
  • 12,111
  • 21
  • 91
  • 136
psaradhi
  • 186
  • 1
  • 1
  • 8

3 Answers3

19

It is easiest to read a single form to and from the file, so I usually put my data into a vector. I also prefer to use pr or pr-str rather than print because it is guaranteed to produce readable data,

(def my-data [{:a "foo" :b [1 2 3]} "asdf" 42 #{1 2 3}]) 
(spit "/tmp/data.edn" (with-out-str (pr my-data)))
nil
(read-string (slurp "/tmp/data.edn"))
[{:a "foo", :b [1 2 3]} "asdf" 42 #{1 2 3}] 

vs:

(spit "/tmp/data.edn" (with-out-str (print my-data)))
(read-string (slurp "/tmp/data.edn"))
[{:a foo, :b [1 2 3]} asdf 42 #{1 2 3}] 

notice how the string `"asdf" was read back as a symbol.

.toString also works fine:

(spit "/tmp/data.edn" (.toString my-data)) 
(read-string (slurp "/tmp/data.edn"))
[{:a "foo", :b [1 2 3]} "asdf" 42 #{1 2 3}] 
Arthur Ulfeldt
  • 90,827
  • 27
  • 201
  • 284
16

Yes - spit and prn-str for writing, slurp and read-string for reading.

user=> (def a [1 2 3 4])
#'user/a
user=> (prn-str a)
"[1 2 3 4]\n"
user=> (spit "stored-array.dat" (prn-str a))
nil

(in a new REPL session)

user=> (require 'clojure.edn)
nil
user=> (slurp "stored-array.dat")
"[1 2 3 4]\n"
user=> (clojure.edn/read-string (slurp "stored-array.dat"))
[1 2 3 4]

I used a vector for that example, but any data (e.g. maps) should work just as well.

Note that there is a read-string in the main Clojure language, but this is explicitly documented as not being safe for untrusted data, so it's better to use the version from clojure.edn.

rkday
  • 1,106
  • 8
  • 12
3

Take a look at this question that deals with print-dup and serializing forms. It has a better coverage of the trade-offs and security issues involved with each method.

Community
  • 1
  • 1
Jared314
  • 5,181
  • 24
  • 30