I tried to follow the documentation for clojure.instant/read-instant-timestamp
, which reads:
clojure.instant/read-instant-timestamp To read an instant as a java.sql.Timestamp, bind *data-readers* to a map with this var as the value for the 'inst key. Timestamp preserves fractional seconds with nanosecond precision. The timezone offset will be used to convert into UTC.`
The following result was unexpected:
(do
(require '[clojure.edn :as edn])
(require '[clojure.instant :refer [read-instant-timestamp]])
(let [instant "#inst \"1970-01-01T00:00:09.999-00:00\""
reader-map {'inst #'read-instant-timestamp}]
;; This binding is not appearing to do anything.
(binding [*data-readers* reader-map]
;; prints java.util.Date -- unexpected
(->> instant edn/read-string class println)
;; prints java.sql.Timestamp -- as desired
(->> instant (edn/read-string {:readers reader-map}) class println))))
How can I use the *data-readers*
binding? Clojure version 1.5.1.
clojure.edn
functions by default only use data readers stored in clojure.core/default-data-readers
which, as of Clojure 1.5.1, provides readers for instant and UUID literals. If you want to use custom readers, you can do that by passing in a :readers
option; in particular, you can pass in *data-readers*
. This is documented in the docstring for clojure.edn/read
(the docstring for clojure.edn/read-string
refers to that for read
).
Here are some examples:
(require '[clojure.edn :as edn])
;; instant literals work out of the box:
(edn/read-string "#inst \"2013-06-08T01:00:00Z\"")
;= #inst "2013-06-08T01:00:00.000-00:00"
;; custom literals can be passed in in the opts map:
(edn/read-string {:readers {'foo identity}} "#foo :asdf")
;= :asdf
;; use current binding of *data-readers*
(edn/read-string {:readers *data-readers*} "...")
(The following section added in response to comments made by Richard Möhn in this GitHub issue's comment thread. The immediate question there is whether it is appropriate for a reader function to call eval
on the data passed in. I am not affiliated with the project in question; please see the ticket for details, as well as Richard's comments on the present answer.)
It is worth adding that *data-readers*
is implicitly populated from any data_readers.{clj,cljc}
files that Clojure finds at the root of the classpath at startup time. This can be convenient (it allows one to use custom tagged literals in Clojure source code and at the REPL), but it does mean that new data readers may appear in there with a change to a single dependency. Using an explicitly constructed reader map with clojure.edn
is a simple way to avoid surprises (which could be particularly nasty when dealing with untrusted input).
(Note that the implicit loading process does not result in any code being loaded immediately, or even when a tag mentioned in *data-readers*
is first encountered; the process which populates *data-readers*
creates empty namespaces with unbound Vars as placeholders, and to actually use those readers one still has to require
the relevant namespaces in user code.)
The *data-readers*
dynamic var seems to apply to the read-string
and read
functions from clojure.core
only.
(require '[clojure.instant :refer [read-instant-timestamp]])
(let [instant "#inst \"1970-01-01T00:00:09.999-00:00\""
reader-map {'inst #'read-instant-timestamp}]
;; This will read a java.util.Date
(->> instant read-string class println)
;; This will read a java.sql.Timestamp
(binding [*data-readers* reader-map]
(->> instant read-string class println)))
Browsing through the source code for clojure.edn
reader here, I couldn't find anything that would indicate that the same *data-readers*
var is used at all there.
clojure.core
's functions read
and read-string
use LispReader
(which uses the value from *data-readers*
), while the functions from clojure.edn
use the EdnReader
.
This edn
library is relatively new in Clojure so that might be the reason why the documentation string is not specific enough regarding edn
vs. core
reader, which can cause this kind of confusion.
Hope it helps.
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