(ns puppetlabs.kitchensink.json "Cheshire related functions This front-ends the common set of core cheshire functions: * generate-string * generate-stream * parse-string * parse-stream This namespace when 'required' will also setup some common JSON encoders globally, so you can avoid doing this for each call." (:require [cheshire.core :as core] [cheshire.generate :as generate] [clj-time.coerce :as coerce] [clj-time.core :as clj-time] [clojure.java.io :as io] [clojure.tools.logging :as log]) (:import com.fasterxml.jackson.core.JsonGenerator)) (defn- clj-time-encoder [data jsonGenerator] (.writeString ^JsonGenerator jsonGenerator ^String (coerce/to-string data))) (def ^:dynamic *datetime-encoder* clj-time-encoder) (defn add-common-json-encoders!* "Non-memoize version of add-common-json-encoders!" [] (when (satisfies? generate/JSONable (clj-time/date-time 1999)) (log/warn "Overriding existing JSONable protocol implementation for org.joda.time.DateTime")) (generate/add-encoder org.joda.time.DateTime (fn [data jsonGenerator] (*datetime-encoder* data jsonGenerator)))) (def ^{:doc "Registers some common encoders for cheshire JSON encoding. This is a memoize function, to avoid unnecessary calls to add-encoder. Ideally this function should be called once in your apply, for example your main class. Encoders currently include: * org.joda.time.DateTime - handled with to-string"} add-common-json-encoders! (memoize add-common-json-encoders!*)) (defmacro with-datetime-encoder "Evaluates the body using the given encoder to serialize DateTime objects to JSON. Requires that `add-common-json-encoders!` from this namespace has already been called, and that nobody else has re-extended org.joda.date.DateTime to cheshire's JSONable protocol in the meantime." [encoder & body] `(binding [*datetime-encoder* ~encoder] ~@body)) (def default-pretty-opts {:date-format "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" :pretty true}) (def ^String generate-string core/generate-string) (def ^String generate-stream core/generate-stream) (defn generate-pretty-string "Thinly wraps cheshire.core/generate-string, adding the clj-time default date format and pretty printing from `default-pretty-opts`" ([obj] (generate-pretty-string obj default-pretty-opts)) ([obj opts] (generate-string obj (merge default-pretty-opts opts)))) (defn generate-pretty-stream "Thinly wraps cheshire.core/generate-stream, adding the clj-time default date format and pretty printing from `default-pretty-opts`" ([obj writer] (generate-pretty-stream obj writer default-pretty-opts)) ([obj writer opts] (generate-stream obj writer (merge default-pretty-opts opts)))) (def parse-string core/parse-string) (def parse-stream core/parse-stream) (defn spit-json "Similar to clojure.core/spit, but writes the Clojure datastructure as JSON to `f`" [f obj & options] (with-open [writer ^java.io.BufferedWriter (apply io/writer f options)] (generate-pretty-stream obj writer)) nil)