cheshire.core.clj Maven / Gradle / Ivy
(ns cheshire.core
"Main encoding and decoding namespace."
(:require [cheshire.factory :as factory]
[cheshire.generate :as gen]
[cheshire.generate-seq :as gen-seq]
[cheshire.parse :as parse])
(:import (com.fasterxml.jackson.core JsonParser JsonFactory
JsonGenerator PrettyPrinter
JsonGenerator$Feature)
(com.fasterxml.jackson.dataformat.smile SmileFactory)
(cheshire.prettyprint CustomPrettyPrinter)
(java.io StringWriter StringReader BufferedReader BufferedWriter
ByteArrayOutputStream OutputStream Reader Writer)))
(defonce default-pretty-print-options
{:indentation " "
:line-break "\n"
:indent-arrays? false
:indent-objects? true
:before-array-values nil
:after-array-values nil
:object-field-value-separator nil})
(defn create-pretty-printer
"Returns an instance of CustomPrettyPrinter based on the configuration
provided as argument"
[options]
(let [effective-opts (merge default-pretty-print-options options)
indentation (:indentation effective-opts)
line-break (:line-break effective-opts)
indent-arrays? (:indent-arrays? effective-opts)
indent-objects? (:indent-objects? effective-opts)
before-array-values (:before-array-values effective-opts)
after-array-values (:after-array-values effective-opts)
object-field-value-separator (:object-field-value-separator effective-opts)
indent-with (condp instance? indentation
String indentation
Long (apply str (repeat indentation " "))
Integer (apply str (repeat indentation " "))
" ")]
(-> (new CustomPrettyPrinter)
(.setIndentation indent-with line-break indent-objects? indent-arrays?)
(.setBeforeArrayValues before-array-values)
(.setAfterArrayValues after-array-values)
(.setObjectFieldValueSeparator object-field-value-separator))))
;; Generators
(defn generate-string
"Returns a JSON-encoding String for the given Clojure object. Takes an
optional date format string that Date objects will be encoded with.
The default date format (in UTC) is: yyyy-MM-dd'T'HH:mm:ss'Z'"
(^String [obj]
(generate-string obj nil))
(^String [obj opt-map]
(let [sw (StringWriter.)
generator (.createGenerator
^JsonFactory (or factory/*json-factory*
factory/json-factory)
^Writer sw)
print-pretty (:pretty opt-map)]
(when print-pretty
(condp instance? print-pretty
Boolean
(.useDefaultPrettyPrinter generator)
clojure.lang.IPersistentMap
(.setPrettyPrinter generator (create-pretty-printer print-pretty))
PrettyPrinter
(.setPrettyPrinter generator print-pretty)
nil))
(when (:escape-non-ascii opt-map)
(.enable generator JsonGenerator$Feature/ESCAPE_NON_ASCII))
(gen/generate generator obj
(or (:date-format opt-map) factory/default-date-format)
(:ex opt-map)
(:key-fn opt-map))
(.flush generator)
(.toString sw))))
(defn generate-stream
"Returns a BufferedWriter for the given Clojure object with the JSON-encoded
data written to the writer. Takes an optional date format string that Date
objects will be encoded with.
The default date format (in UTC) is: yyyy-MM-dd'T'HH:mm:ss'Z'"
(^BufferedWriter [obj ^BufferedWriter writer]
(generate-stream obj writer nil))
(^BufferedWriter [obj ^BufferedWriter writer opt-map]
(let [generator (.createGenerator
^JsonFactory (or factory/*json-factory*
factory/json-factory)
^Writer writer)
print-pretty (:pretty opt-map)]
(when print-pretty
(condp instance? print-pretty
Boolean
(.useDefaultPrettyPrinter generator)
clojure.lang.IPersistentMap
(.setPrettyPrinter generator (create-pretty-printer print-pretty))
PrettyPrinter
(.setPrettyPrinter generator print-pretty)
nil))
(when (:escape-non-ascii opt-map)
(.enable generator JsonGenerator$Feature/ESCAPE_NON_ASCII))
(gen/generate generator obj (or (:date-format opt-map)
factory/default-date-format)
(:ex opt-map)
(:key-fn opt-map))
(.flush generator)
writer)))
(defn create-generator [writer]
"Returns JsonGenerator for given writer."
(.createGenerator
^JsonFactory (or factory/*json-factory*
factory/json-factory)
^Writer writer))
(def ^:dynamic ^JsonGenerator *generator*)
(def ^:dynamic *opt-map*)
(defmacro with-writer [[writer opt-map] & body]
"Start writing for series objects using the same json generator.
Takes writer and options map as arguments.
Expects its body as sequence of write calls.
Returns a given writer."
`(let [c-wr# ~writer]
(binding [*generator* (create-generator c-wr#)
*opt-map* ~opt-map]
~@body
(.flush *generator*)
c-wr#)))
(defn write
"Write given Clojure object as a piece of data within with-writer.
List of wholeness acceptable values:
- no value - the same as :all
- :all - write object in a regular way with start and end borders
- :start - write object with start border only
- :start-inner - write object and its inner object with start border only
- :end - write object with end border only."
([obj] (write obj nil))
([obj wholeness]
(gen-seq/generate *generator* obj (or (:date-format *opt-map*)
factory/default-date-format)
(:ex *opt-map*)
(:key-fn *opt-map*)
:wholeness wholeness)))
(defn generate-smile
"Returns a SMILE-encoded byte-array for the given Clojure object.
Takes an optional date format string that Date objects will be encoded with.
The default date format (in UTC) is: yyyy-MM-dd'T'HH:mm:ss'Z'"
(^bytes [obj]
(generate-smile obj nil))
(^bytes [obj opt-map]
(let [baos (ByteArrayOutputStream.)
generator (.createGenerator ^SmileFactory
(or factory/*smile-factory*
factory/smile-factory)
^OutputStream baos)]
(gen/generate generator obj (or (:date-format opt-map)
factory/default-date-format)
(:ex opt-map)
(:key-fn opt-map))
(.flush generator)
(.toByteArray baos))))
(defn generate-cbor
"Returns a CBOR-encoded byte-array for the given Clojure object.
Takes an optional date format string that Date objects will be encoded with.
The default date format (in UTC) is: yyyy-MM-dd'T'HH:mm:ss'Z'"
(^bytes [obj]
(generate-cbor obj nil))
(^bytes [obj opt-map]
(let [baos (ByteArrayOutputStream.)
generator (.createGenerator ^CBORFactory
(or factory/*cbor-factory*
factory/cbor-factory)
^OutputStream baos)]
(gen/generate generator obj (or (:date-format opt-map)
factory/default-date-format)
(:ex opt-map)
(:key-fn opt-map))
(.flush generator)
(.toByteArray baos))))
;; Parsers
(defn parse-string
"Returns the Clojure object corresponding to the given JSON-encoded string.
An optional key-fn argument can be either true (to coerce keys to keywords),
false to leave them as strings, or a function to provide custom coercion.
The array-coerce-fn is an optional function taking the name of an array field,
and returning the collection to be used for array values.
If the top-level object is an array, it will be parsed lazily (use
`parse-strict' if strict parsing is required for top-level arrays."
([string] (parse-string string nil nil))
([string key-fn] (parse-string string key-fn nil))
([^String string key-fn array-coerce-fn]
(when string
(parse/parse
(.createParser ^JsonFactory (or factory/*json-factory*
factory/json-factory)
^Reader (StringReader. string))
key-fn nil array-coerce-fn))))
;; Parsing strictly
(defn parse-string-strict
"Returns the Clojure object corresponding to the given JSON-encoded string.
An optional key-fn argument can be either true (to coerce keys to keywords),
false to leave them as strings, or a function to provide custom coercion.
The array-coerce-fn is an optional function taking the name of an array field,
and returning the collection to be used for array values.
Does not lazily parse top-level arrays."
([string] (parse-string-strict string nil nil))
([string key-fn] (parse-string-strict string key-fn nil))
([^String string key-fn array-coerce-fn]
(when string
(parse/parse-strict
(.createParser ^JsonFactory (or factory/*json-factory*
factory/json-factory)
^Writer (StringReader. string))
key-fn nil array-coerce-fn))))
(defn parse-stream
"Returns the Clojure object corresponding to the given reader, reader must
implement BufferedReader. An optional key-fn argument can be either true (to
coerce keys to keywords),false to leave them as strings, or a function to
provide custom coercion.
The array-coerce-fn is an optional function taking the name of an array field,
and returning the collection to be used for array values.
If the top-level object is an array, it will be parsed lazily (use
`parse-strict' if strict parsing is required for top-level arrays.
If multiple objects (enclosed in a top-level `{}' need to be parsed lazily,
see parsed-seq."
([rdr] (parse-stream rdr nil nil))
([rdr key-fn] (parse-stream rdr key-fn nil))
([^BufferedReader rdr key-fn array-coerce-fn]
(when rdr
(parse/parse
(.createParser ^JsonFactory (or factory/*json-factory*
factory/json-factory)
^Reader rdr)
key-fn nil array-coerce-fn))))
(defn parse-smile
"Returns the Clojure object corresponding to the given SMILE-encoded bytes.
An optional key-fn argument can be either true (to coerce keys to keywords),
false to leave them as strings, or a function to provide custom coercion.
The array-coerce-fn is an optional function taking the name of an array field,
and returning the collection to be used for array values."
([bytes] (parse-smile bytes nil nil))
([bytes key-fn] (parse-smile bytes key-fn nil))
([^bytes bytes key-fn array-coerce-fn]
(when bytes
(parse/parse
(.createParser ^SmileFactory (or factory/*smile-factory*
factory/smile-factory) bytes)
key-fn nil array-coerce-fn))))
(defn parse-cbor
"Returns the Clojure object corresponding to the given CBOR-encoded bytes.
An optional key-fn argument can be either true (to coerce keys to keywords),
false to leave them as strings, or a function to provide custom coercion.
The array-coerce-fn is an optional function taking the name of an array field,
and returning the collection to be used for array values."
([bytes] (parse-cbor bytes nil nil))
([bytes key-fn] (parse-cbor bytes key-fn nil))
([^bytes bytes key-fn array-coerce-fn]
(when bytes
(parse/parse
(.createParser ^CBORFactory (or factory/*cbor-factory*
factory/cbor-factory) bytes)
key-fn nil array-coerce-fn))))
(def ^{:doc "Object used to determine end of lazy parsing attempt."}
eof (Object.))
;; Lazy parsers
(defn- parsed-seq*
"Internal lazy-seq parser"
[^JsonParser parser key-fn array-coerce-fn]
(lazy-seq
(let [elem (parse/parse-strict parser key-fn eof array-coerce-fn)]
(when-not (identical? elem eof)
(cons elem (parsed-seq* parser key-fn array-coerce-fn))))))
(defn parsed-seq
"Returns a lazy seq of Clojure objects corresponding to the JSON read from
the given reader. The seq continues until the end of the reader is reached.
The array-coerce-fn is an optional function taking the name of an array field,
and returning the collection to be used for array values.
If non-laziness is needed, see parse-stream."
([reader] (parsed-seq reader nil nil))
([reader key-fn] (parsed-seq reader key-fn nil))
([^BufferedReader reader key-fn array-coerce-fn]
(when reader
(parsed-seq* (.createParser ^JsonFactory
(or factory/*json-factory*
factory/json-factory)
^Reader reader)
key-fn array-coerce-fn))))
(defn parsed-smile-seq
"Returns a lazy seq of Clojure objects corresponding to the SMILE read from
the given reader. The seq continues until the end of the reader is reached.
The array-coerce-fn is an optional function taking the name of an array field,
and returning the collection to be used for array values."
([reader] (parsed-smile-seq reader nil nil))
([reader key-fn] (parsed-smile-seq reader key-fn nil))
([^BufferedReader reader key-fn array-coerce-fn]
(when reader
(parsed-seq* (.createParser ^SmileFactory
(or factory/*smile-factory*
factory/smile-factory)
^Reader reader)
key-fn array-coerce-fn))))
;; aliases for clojure-json users
(defmacro copy-arglists
[dst src]
`(alter-meta! (var ~dst) merge (select-keys (meta (var ~src)) [:arglists])))
(def encode "Alias to generate-string for clojure-json users" generate-string)
(copy-arglists encode generate-string)
(def encode-stream "Alias to generate-stream for clojure-json users" generate-stream)
(copy-arglists encode-stream generate-stream)
(def encode-smile "Alias to generate-smile for clojure-json users" generate-smile)
(copy-arglists encode-smile generate-smile)
(def decode "Alias to parse-string for clojure-json users" parse-string)
(copy-arglists decode parse-string)
(def decode-strict "Alias to parse-string-strict for clojure-json users" parse-string-strict)
(copy-arglists decode-strict parse-string-strict)
(def decode-stream "Alias to parse-stream for clojure-json users" parse-stream)
(copy-arglists decode-stream parse-stream)
(def decode-smile "Alias to parse-smile for clojure-json users" parse-smile)
(copy-arglists decode-smile parse-smile)