Functions for encoding and decoding JSON that abstract away the underlying implementation.

(ns metabase.util.json
  (:require
   #_{:clj-kondo/ignore [:discouraged-namespace]}
   [cheshire.core :as cheshire]
   [cheshire.factory]
   [cheshire.generate :as json.generate]
   [clojure.java.io :as io])
  (:import
   (com.fasterxml.jackson.core JsonGenerator)
   (java.io InputStream Reader)))
(set! *warn-on-reflection* true)

Register a custom encoder for class.

(defn add-encoder
  [class encoder]
  (json.generate/add-encoder class encoder))

Returns JsonGenerator for given writer.

(defn create-generator
  [writer]
  (cheshire/create-generator writer))

Default date formatter for JSON serialization.

Tell the JSON middleware to use a date format that includes milliseconds (why?)

(def default-date-format  "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
(alter-var-root #'cheshire.factory/default-date-format (constantly default-date-format))
(alter-var-root #'json.generate/*date-format* (constantly default-date-format))

Call cheshire.generate/generate.

(defn generate
  [jg obj date-format ex key-fn]
  (json.generate/generate jg obj date-format ex key-fn))

Encode a clojure map to the json generator.

(defn generate-map
  [m jg]
  (json.generate/encode-map m jg))

Encode null to the json generator.

(defn generate-nil
  [_ ^JsonGenerator jg]
  (.writeNull jg))

Wrap a string so it will be spliced directly into resulting JSON as-is. Analogous to HoneySQL raw.

(defn raw-json-generator
  [^String s]
  (reify json.generate/JSONable
    (to-json [_ generator]
      (.writeRawValue ^JsonGenerator generator s))))

Return true if the type of the given value has a registered custom implementation of JSONable.

(defn has-custom-encoder?
  [value]
  (contains? (:impls json.generate/JSONable) (type value)))

Encode string to the json generator.

(defn write-string
  [^JsonGenerator jg ^String str]
  (json.generate/write-string jg str))

Return a JSON-encoding String for the given object.

(defn encode
  (^String [obj]
   (cheshire/generate-string obj))
  (^String [obj opt-map]
   (cheshire/generate-string obj opt-map)))

Encode a value as JSON and write to the given Writer object.

(defn encode-to
  [obj writer opt-map]
  (cheshire/generate-stream obj writer opt-map))

Decode a value from a JSON from a string, InputStream, or Reader.

(defn decode
  ([source] (decode source nil))
  ([source key-fn]
   (cond (string? source) (cheshire/parse-string source key-fn)
         (instance? Reader source) (cheshire/parse-stream source key-fn)
         (instance? InputStream source) (cheshire/parse-stream (io/reader source) key-fn)
         (nil? source) nil
         :else (throw (ex-info (str "Unsupported source type: " (type source)) {})))))

Decode a value from a JSON from a string, InputStream, or Reader, keywordizing map keys.

(defn decode+kw
  [source]
  (decode source true))