Mark Needham

Thoughts on Software Development

Clojure: Writing JSON to a file – “Exception Don’t know how to write JSON of class org.joda.time.DateTime”

with 4 comments

As I mentioned in an earlier post I’ve been transforming Clojure hash’s into JSON strings using data.json but ran into trouble while trying to parse a hash which contained a Joda Time DateTime instance.

The date in question was constructed like this:

(ns json-date-example
  (:require [clj-time.format :as f])
  (:require [clojure.data.json :as json]))
 
(defn as-date [date-field]
  (f/parse (f/formatter "dd MMM YYYY") date-field ))
 
(def my-date 
  (as-date "18 Mar 2012"))

And when I tried to convert a hash containing that object into a string I got the following exception:

> (json/write-str {:date my-date)})
 
java.lang.Exception: Don't know how to write JSON of class org.joda.time.DateTime
 at clojure.data.json$write_generic.invoke (json.clj:367)
    clojure.data.json$eval2818$fn__2819$G__2809__2826.invoke (json.clj:284)
    clojure.data.json$write_object.invoke (json.clj:333)
    clojure.data.json$eval2818$fn__2819$G__2809__2826.invoke (json.clj:284)
    clojure.data.json$write.doInvoke (json.clj:450)
    clojure.lang.RestFn.invoke (RestFn.java:425)

Luckily it’s quite easy to get around this by passing a function to write-str that converts the DateTime into a string representation before writing that part of the hash to a string.

The function looks like this:

(defn as-date-string [date]
  (f/unparse (f/formatter "dd MMM YYYY") date))
 
(defn date-aware-value-writer [key value] 
  (if (= key :date) (as-date-string value) value))

And we make use of the writer like so:

> (json/write-str {:date my-date} :value-fn date-aware-value-writer)
"{\"date\":\"18 Mar 2012\"}"

If we want to read that string back again and reify our date we create a reader function which converts a string into a DateTime. The as-date function from the beginning of this post does exactly what we want so we’ll use that:

(defn date-aware-value-reader [key value] 
  (if (= key :date) (as-date value) value))

We can then pass the reader as an argument to read-str:

> (json/read-str "{\"date\":\"18 Mar 2012\"}" :value-fn date-aware-value-reader :key-fn keyword)
{:date #<DateTime 2012-03-18T00:00:00.000Z>}
Be Sociable, Share!

Written by Mark Needham

September 26th, 2013 at 7:11 pm

Posted in Clojure

Tagged with

  • Why embed “18 Mar 2012” into a JSON string? You should use something more standard like “2013-03-18”, which is ISO8601 format. Also – if it’s a Joda DateTime, then it carries a time zone context, which you are dropping. Later you are showing it as “2012-03-18T00:00:00.000Z” which puts that date at midnight in UTC – which might not have been what you started with. I think the main issue is that you have a DateTime but you are treating it as if it were a LocalDate. You may wish to read through the docs at http://www.joda.org/joda-time

  • @mj1856:disqus good points…although in this case as long as the date is correct I’m not really bothered about the time. I probably should have used a different type from the JodaTime library to represent it but this approach solved the problem I had.

  • sumek83

    Hey Mark,
    you could also extend the JSONWriter protocol. That way there is no need to check the keys of the map and it serialises every occurence of the Joda DateTime.

    (extend org.joda.time.DateTime json/JSONWriter {:-write (fn [in ^PrintWriter out] (.print out (coerce/to-string in)))})

  • @sumek83:disqus neat, didn’t know about that approach. Much cleaner – thanks for sharing 🙂