13

Clojure 1.5 introduced clojure.edn, which includes a read function that requires a PushbackReader.

If I want to read the first five objects, I can do:

(with-open [infile (java.io.PushbackReader. (clojure.java.io/reader "foo.txt"))]
  (binding [*in* infile]
    (let [edn-seq (repeatedly clojure.edn/read)]
      (dorun (take 5 (map println edn-seq))))))

How can I instead print out all of the objects? Considering that some of them may be nils, it seems like I need to check for the EOF, or something similar. I want to have a sequence of objects similar to what I would get from line-seq.

A. Webb
  • 26,227
  • 1
  • 63
  • 95
ToBeReplaced
  • 3,334
  • 2
  • 26
  • 42

2 Answers2

16

Use :eof key

https://clojure.github.io/clojure/clojure.edn-api.html

opts is a map that can include the following keys: :eof - value to return on end-of-file. When not supplied, eof throws an exception.

edit: sorry, that wasn't enough detail! here y'go:

(with-open [in (java.io.PushbackReader. (clojure.java.io/reader "foo.txt"))]
  (let [edn-seq (repeatedly (partial edn/read {:eof :theend} in))]
    (dorun (map println (take-while (partial not= :theend) edn-seq)))))

that should do it

Sergio
  • 3,317
  • 5
  • 32
  • 51
Hendekagon
  • 4,565
  • 2
  • 28
  • 43
  • 1
    Yeah, it was right there in the documentation. Whoops. It feels kinda dirty to have to define my own sentinel for EOF,but maybe I just haven't grokked why that's the right solution yet. – ToBeReplaced Mar 06 '13 at 04:08
  • See comments by @bfontaine on other answer. There is value in having content-independence, but you can achieve that by using any value for the `:eof` key that is not serializable as EDN. – ToBeReplaced Oct 11 '16 at 18:27
3

I looked at this again. Here is what I came up with:

(defn edn-seq
  "Returns the objects from stream as a lazy sequence."
  ([]
     (edn-seq *in*))
  ([stream]
     (edn-seq {} stream))
  ([opts stream]
     (lazy-seq (cons (clojure.edn/read opts stream) (edn-seq opts stream)))))

(defn swallow-eof
  "Ignore an EOF exception raised when consuming seq."
  [seq]
  (-> (try
        (cons (first seq) (swallow-eof (rest seq)))
        (catch java.lang.RuntimeException e
          (when-not (= (.getMessage e) "EOF while reading")
            (throw e))))
      lazy-seq))

(with-open [stream (java.io.PushbackReader. (clojure.java.io/reader "foo.txt"))]
  (dorun (map println (swallow-eof (edn-seq stream)))))

edn-seq has the same signature as clojure.edn/read, and preserves all of the existing behavior, which I think is important given that people may use the :eof option in different ways. A separate function to contain the EOF exception seemed like a better choice, though I'm not sure how best to capture it since it shows up just as a java.lang.RuntimeException.

ToBeReplaced
  • 3,334
  • 2
  • 26
  • 42
  • Use the `:eof` argument instead of testing on the exception’s text. – bfontaine Oct 10 '16 at 10:27
  • Old question/answer, but why do you say that? If you implement with `:eof`, then you aren't content-independent. Even `:library/eof` could collide. Only way to avoid would be to force the user to pass in an :eof value themselves as part of `swallow-eof`. – ToBeReplaced Oct 11 '16 at 05:50
  • EDN is a subset of Clojure values so all you have to do is give a Clojure value that’s not serializable as EDN, like a function. – bfontaine Oct 11 '16 at 13:59
  • Good point. That seems like the right way to do it. I tried to give an example, but formatting in the comments is tricky. Thanks! – ToBeReplaced Oct 11 '16 at 18:23