Skip to content

Commit

Permalink
allow a custom keyword fn to munge JSON keys
Browse files Browse the repository at this point in the history
  • Loading branch information
dakrone committed Aug 30, 2012
1 parent bacb3ac commit 52a700b
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 28 deletions.
28 changes: 13 additions & 15 deletions src/cheshire/core.clj
Expand Up @@ -75,12 +75,12 @@
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."
[^String string & [^Boolean keywords? array-coerce-fn]]
[^String string & [key-fn array-coerce-fn]]
(when string
(parse
(.createJsonParser ^JsonFactory (or *json-factory* json-factory)
(StringReader. string))
true (or keywords? false) nil array-coerce-fn)))
key-fn nil array-coerce-fn)))

(defn parse-stream
"Returns the Clojure object corresponding to the given reader, reader must
Expand All @@ -90,11 +90,11 @@
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 laziness is needed, see parsed-seq."
[^BufferedReader rdr & [^Boolean keywords? array-coerce-fn]]
[^BufferedReader rdr & [key-fn array-coerce-fn]]
(when rdr
(parse
(.createJsonParser ^JsonFactory (or *json-factory* json-factory) rdr)
true (or keywords? false) nil array-coerce-fn)))
key-fn nil array-coerce-fn)))

(defn parse-smile
"Returns the Clojure object corresponding to the given SMILE-encoded bytes.
Expand All @@ -103,23 +103,23 @@
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 bytes & [^Boolean keywords? array-coerce-fn]]
[^bytes bytes & [key-fn array-coerce-fn]]
(when bytes
(parse
(.createJsonParser ^SmileFactory (or *smile-factory* smile-factory) bytes)
true (or keywords? false) nil array-coerce-fn)))
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 ^Boolean keywords? array-coerce-fn]
[^JsonParser parser key-fn array-coerce-fn]
(lazy-seq
(let [elem (parse parser true keywords? eof array-coerce-fn)]
(let [elem (parse parser key-fn eof array-coerce-fn)]
(when-not (identical? elem eof)
(cons elem (parsed-seq* parser keywords? array-coerce-fn))))))
(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
Expand All @@ -128,25 +128,23 @@
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."
[^BufferedReader reader & [^Boolean keywords? array-coerce-fn]]
[^BufferedReader reader & [key-fn array-coerce-fn]]
(when reader
(parsed-seq* (.createJsonParser ^JsonFactory
(or *json-factory* json-factory) reader)
(or keywords? false)
array-coerce-fn)))
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."
[^BufferedReader reader & [^Boolean keywords? array-coerce-fn]]
[^BufferedReader reader & [key-fn array-coerce-fn]]
(when reader
(parsed-seq* (.createJsonParser ^SmileFactory
(or *smile-factory* smile-factory) reader)
(or keywords? false)
array-coerce-fn)))
key-fn array-coerce-fn)))

;; aliases for clojure-json users
(def encode generate-string)
Expand Down
25 changes: 12 additions & 13 deletions src/cheshire/parse.clj
Expand Up @@ -8,40 +8,39 @@
:dynamic true}
*use-bigdecimals?* false)

(definline parse-object [^JsonParser jp keywords? bd? array-coerce-fn]
(definline parse-object [^JsonParser jp key-fn bd? array-coerce-fn]
`(do
(.nextToken ~jp)
(loop [mmap# (transient {})]
(if-not (= (.getCurrentToken ~jp)
JsonToken/END_OBJECT)
(let [key-str# (.getText ~jp)
_# (.nextToken ~jp)
key# (if ~keywords?
(keyword key-str#)
key-str#)
key# (~key-fn key-str#)
mmap# (assoc! mmap# key#
(parse* ~jp ~keywords? ~bd? ~array-coerce-fn))]
(parse* ~jp ~key-fn ~bd? ~array-coerce-fn))]
(.nextToken ~jp)
(recur mmap#))
(persistent! mmap#)))))

(definline parse-array [^JsonParser jp keywords? bd? array-coerce-fn]
(definline parse-array [^JsonParser jp key-fn bd? array-coerce-fn]
`(let [array-field-name# (.getCurrentName ~jp)]
(.nextToken ~jp)
(loop [coll# (transient (if ~array-coerce-fn
(~array-coerce-fn array-field-name#)
[]))]
(if-not (= (.getCurrentToken ~jp)
JsonToken/END_ARRAY)
(let [coll# (conj! coll#(parse* ~jp ~keywords? ~bd? ~array-coerce-fn))]
(let [coll# (conj! coll#
(parse* ~jp ~key-fn ~bd? ~array-coerce-fn))]
(.nextToken ~jp)
(recur coll#))
(persistent! coll#)))))

(defn parse* [^JsonParser jp keywords? bd? array-coerce-fn]
(defn parse* [^JsonParser jp key-fn bd? array-coerce-fn]
(condp = (.getCurrentToken jp)
JsonToken/START_OBJECT (parse-object jp keywords? bd? array-coerce-fn)
JsonToken/START_ARRAY (parse-array jp keywords? bd? array-coerce-fn)
JsonToken/START_OBJECT (parse-object jp key-fn bd? array-coerce-fn)
JsonToken/START_ARRAY (parse-array jp key-fn bd? array-coerce-fn)
JsonToken/VALUE_STRING (.getText jp)
JsonToken/VALUE_NUMBER_INT (.getNumberValue jp)
JsonToken/VALUE_NUMBER_FLOAT (if bd?
Expand All @@ -54,9 +53,9 @@
(Exception.
(str "Cannot parse " (pr-str (.getCurrentToken jp)))))))

(defn parse [^JsonParser jp fst? keywords? eof array-coerce-fn]
(let [keywords? (boolean keywords?)]
(defn parse [^JsonParser jp key-fn eof array-coerce-fn]
(let [key-fn (or (if (= key-fn true) keyword key-fn) identity)]
(.nextToken jp)
(if (nil? (.getCurrentToken jp))
eof
(parse* jp keywords? *use-bigdecimals?* array-coerce-fn))))
(parse* jp key-fn *use-bigdecimals?* array-coerce-fn))))
7 changes: 7 additions & 0 deletions test/cheshire/test/core.clj
Expand Up @@ -187,3 +187,10 @@
(deftest t-unicode-escaping
(is (= "{\"foo\":\"It costs \\u00A3100\"}"
(json/encode {:foo "It costs £100"} {:escape-non-ascii true}))))

(deftest t-custom-keyword-fn
(is (= {:FOO "bar"} (json/decode "{\"foo\": \"bar\"}"
(fn [k] (keyword (.toUpperCase k))))))
(is (= {"foo" "bar"} (json/decode "{\"foo\": \"bar\"}" nil)))
(is (= {"foo" "bar"} (json/decode "{\"foo\": \"bar\"}" false)))
(is (= {:foo "bar"} (json/decode "{\"foo\": \"bar\"}" true))))

0 comments on commit 52a700b

Please sign in to comment.