R – Clojure: slurping structs from file fails with string attributes containing whitespaces

clojure

I have just started playing with Clojure and the first thing I thought I'd try is storing and retrieving a list of structs, like in Suart Halloway's example here.

My spit/slurp of a hash of structs works fine with, if I use struct instances without spaces in the attribute strings like the following:

(struct customer "Apple" "InfiniteLoop")

But if I use this:

(struct customer "Apple" "Infinite Loop 1")

I get an error:

Exception in thread "main" clojure.lang.LispReader$ReaderException: java.lang.ArrayIndexOutOfBoundsException: 7 (test-storing.clj:19)    at clojure.lang.Compiler$InvokeExpr.eval(Compiler.java:2719)    at clojure.lang.Compiler$DefExpr.eval(Compiler.java:298)    at clojure.lang.Compiler.eval(Compiler.java:4537)    at clojure.lang.Compiler.load(Compiler.java:4857)    at clojure.lang.Compiler.loadFile(Compiler.java:4824)    at clojure.main$load_script__5833.invoke(main.clj:206)    at clojure.main$init_opt__5836.invoke(main.clj:211)    at clojure.main$initialize__5846.invoke(main.clj:239)    at clojure.main$null_opt__5868.invoke(main.clj:264)    at clojure.main$legacy_script__5883.invoke(main.clj:295)    at clojure.lang.Var.invoke(Var.java:346)    at clojure.main.legacy_script(main.java:34)    at clojure.lang.Script.main(Script.java:20)Caused by: clojure.lang.LispReader$ReaderException: java.lang.ArrayIndexOutOfBoundsException: 7    at clojure.lang.LispReader.read(LispReader.java:180)    at clojure.core$read__4168.invoke(core.clj:2083)    at clojure.core$read__4168.invoke(core.clj:2081)    at clojure.core$read__4168.invoke(core.clj:2079)    at clojure.core$read__4168.invoke(core.clj:2077)    at chap_03$load_db__54.invoke(chap_03.clj:71)    at clojure.lang.AFn.applyToHelper(AFn.java:173)    at clojure.lang.AFn.applyTo(AFn.java:164)    at clojure.lang.Compiler$InvokeExpr.eval(Compiler.java:2714)    ... 12 moreCaused by: java.lang.ArrayIndexOutOfBoundsException: 7    at clojure.lang.PersistentArrayMap$Seq.first(PersistentArrayMap.java:216)    at clojure.lang.APersistentMap.hashCode(APersistentMap.java:101)    at clojure.lang.Util.hash(Util.java:55)    at clojure.lang.PersistentHashMap.entryAt(PersistentHashMap.java:134)    at clojure.lang.PersistentHashMap.containsKey(PersistentHashMap.java:130)    at clojure.lang.APersistentSet.contains(APersistentSet.java:33)    at clojure.lang.PersistentHashSet.cons(PersistentHashSet.java:59)    at clojure.lang.PersistentHashSet.create(PersistentHashSet.java:34)    at clojure.lang.LispReader$SetReader.invoke(LispReader.java:974)    at clojure.lang.LispReader$DispatchReader.invoke(LispReader.java:540)    at clojure.lang.LispReader.read(LispReader.java:145)    ... 20 more

Depending on the amount of the fields in the struct, I might also just get a part of the string as an attribute name instead of the error. For example :Loop 1

I use a store-function like this:

(defn store-customer-db [customer-db filename]  (spit filename (with-out-str (print customer-db))))

And a read-function like this:

(defn load-db [filename]  (with-in-str (slurp filename)(read)))

From the output file of spit I can see that the print doesn't give double quotes to the strings which seems to be a problem for slurp. What would be the correct solution for this?

My Clojure version is 1.0, and the contrib is a few weeks old snapshot.

Best Solution

print and println are meant for human-readable output. If you want to print something that's meant to be read in again later, use pr or prn.

user> (read-string (with-out-str (prn {"Apple" "Infinite Loop"}))){"Apple" "Infinite Loop"}

Whereas:

user> (read-string (with-out-str (print {"Apple" "Infinite Loop"})))java.lang.ArrayIndexOutOfBoundsException: 3 (NO_SOURCE_FILE:0)

It's trying to run this code:

(read-string "{Apple Infinite Loop}")

which has an odd number of keys/values. Note the lack of quotation marks around the individual hash keys/values. Even if this read works (i.e. if you coincidentally supply an even number of parameters), what it reads won't be a hash-map full of Strings, but rather Symbols. So you'll be getting back something other than what you output.

user> (map class (keys (read-string (with-out-str (print {"foo bar" "baz quux"})))))(clojure.lang.Symbol clojure.lang.Symbol)