diff --git a/project.clj b/project.clj index 0c0458a..053a276 100644 --- a/project.clj +++ b/project.clj @@ -11,7 +11,8 @@ :sign-releases false}]] :plugins [[lein-cljfmt "0.6.4"]] :global-vars {*warn-on-reflection* true} - :profiles {:dev {:dependencies [[org.clojure/clojure "1.10.1"] + :profiles {:dev {:dependencies [[org.clojure/clojure "1.11.1"] + [org.clojure/data.json "2.4.0"] [cheshire "5.10.0"] [ring/ring-core "1.9.0"] [javax.servlet/servlet-api "2.5"] diff --git a/src/hato/middleware.clj b/src/hato/middleware.clj index a7435f3..9e882dc 100644 --- a/src/hato/middleware.clj +++ b/src/hato/middleware.clj @@ -22,15 +22,25 @@ (java.util.zip GZIPInputStream InflaterInputStream ZipException Inflater))) -;; Cheshire is an optional dependency, so we check for it at compile time. +(def ^:dynamic *json-lib* nil) +(defmulti decode-json + (fn [[lib & _]] + lib)) -(def json-enabled? - (try - (require - 'cheshire.core) - true - (catch Throwable _ false))) +(defmethod decode-json :default [& _] + (throw (ex-info "JSON library not loaded!" + {:type :json-lib-not-loaded + :json-lib-value *json-lib*}))) + +(defmulti encode-json + (fn [[lib & _]] + lib)) + +(defmethod encode-json :default [& _] + (throw (ex-info "JSON library not loaded!" + {:type :json-lib-not-loaded + :json-lib-value *json-lib*}))) ;; Transit is an optional dependency, so check at compile time. (def transit-enabled? @@ -69,14 +79,12 @@ (defn ^:dynamic json-encode "Resolve and apply cheshire's json encoding dynamically." [& args] - {:pre [json-enabled?]} - (apply (ns-resolve (symbol "cheshire.core") (symbol "encode")) args)) + (encode-json (cons *json-lib* args))) (defn ^:dynamic json-decode-stream-strict "Resolve and apply cheshire's json decoding dynamically (with lazy parsing disabled)." [& args] - {:pre [json-enabled?]} - (apply (ns-resolve (symbol "cheshire.core") (symbol "parse-stream-strict")) args)) + (decode-json (cons *json-lib* args))) ;;; @@ -168,7 +176,7 @@ [{:keys [coerce]} {:keys [body status] :as resp} keyword?] (let [^String charset (or (-> resp :content-type-params :charset) "UTF-8") decode-func json-decode-stream-strict] - (if json-enabled? + (if *json-lib* (cond (and (unexceptional-status? status) (or (nil? coerce) (= coerce :unexceptional))) @@ -501,10 +509,10 @@ (defmethod coerce-form-params :application/json [{:keys [form-params json-opts]}] - (when-not json-enabled? + (when-not *json-lib* (throw (ex-info (str "Can't encode form params as \"application/json\". " - "Cheshire dependency not loaded.") - {:type :cheshire-not-loaded + "Requires cheshire or clojure.data.json library.") + {:type :json-lib-not-loaded :form-params form-params :json-opts json-opts}))) (json-encode form-params json-opts)) diff --git a/src/hato/optional/cheshire.clj b/src/hato/optional/cheshire.clj new file mode 100644 index 0000000..a93c032 --- /dev/null +++ b/src/hato/optional/cheshire.clj @@ -0,0 +1,11 @@ +(ns hato.optional.cheshire + (:require [cheshire.core :as cheshire] + [hato.middleware :refer [decode-json encode-json]])) + +(defmethod decode-json 'cheshire [[_ & args]] + (apply cheshire/parse-stream-strict args)) + +(defmethod encode-json 'cheshire [[_ & args]] + (apply cheshire/encode args)) + +(alter-var-root #'hato.middleware/*json-lib* (constantly 'cheshire)) diff --git a/src/hato/optional/data_json.clj b/src/hato/optional/data_json.clj new file mode 100644 index 0000000..0c42fee --- /dev/null +++ b/src/hato/optional/data_json.clj @@ -0,0 +1,16 @@ +(ns hato.optional.data-json + (:require [clojure.data.json :as json] + [hato.middleware :refer [decode-json encode-json]])) + +(defmethod decode-json 'clojure.data.json [[_ reader key-fn & args]] + (apply json/read reader + :key-fn (if (true? key-fn) + keyword + identity) + args)) + +(defmethod encode-json 'clojure.data.json [[_ & args]] + ;; Mimicks the behavior of cheshire.core/encode + (apply json/write-str args)) + +(alter-var-root #'hato.middleware/*json-lib* (constantly 'clojure.data.json))