diff --git a/src/ring/middleware/json.clj b/src/ring/middleware/json.clj index e89daf6..6f2d3a2 100644 --- a/src/ring/middleware/json.clj +++ b/src/ring/middleware/json.clj @@ -13,7 +13,7 @@ (if-let [type (get-in request [:headers "content-type"])] (not (empty? (re-find #"^application/(.+\+)?json" type))))) -(defn- read-json [request & [{:keys [keywords? bigdecimals?]}]] +(defn- read-json [request & [{:keys [keywords? bigdecimals? key-fn]}]] (if (json-request? request) (if-let [^InputStream body (:body request)] (let [^String encoding (or (character-encoding request) @@ -21,7 +21,7 @@ body-reader (java.io.InputStreamReader. body encoding)] (binding [parse/*use-bigdecimals?* bigdecimals?] (try - [true (json/parse-stream body-reader keywords?)] + [true (json/parse-stream body-reader (or key-fn keywords?))] (catch com.fasterxml.jackson.core.JsonParseException ex [false nil]))))))) @@ -34,8 +34,8 @@ (defn json-body-request "Parse a JSON request body and assoc it back into the :body key. Returns nil if the JSON is malformed. See: wrap-json-body." - [request {:keys [keywords? bigdecimals?]}] - (if-let [[valid? json] (read-json request {:keywords? keywords? :bigdecimals? bigdecimals?})] + [request options] + (if-let [[valid? json] (read-json request options)] (if valid? (assoc request :body json)) request)) @@ -46,6 +46,7 @@ Accepts the following options: + :key-fn - function that will be applied to each key :keywords? - true if the keys of maps should be turned into keywords :bigdecimals? - true if BigDecimals should be used instead of Doubles :malformed-response - a response map to return when the JSON is malformed" @@ -74,8 +75,8 @@ "Parse the body of JSON requests into a map of parameters, which are added to the request map on the :json-params and :params keys. Returns nil if the JSON is malformed. See: wrap-json-params." - [request {:keys [bigdecimals?]}] - (if-let [[valid? json] (read-json request {:bigdecimals? bigdecimals?})] + [request options] + (if-let [[valid? json] (read-json request options)] (if valid? (assoc-json-params request json)) request)) @@ -85,6 +86,7 @@ Accepts the following options: + :key-fn - function that will be applied to each key :bigdecimals? - true if BigDecimals should be used instead of Doubles :malformed-response - a response map to return when the JSON is malformed @@ -130,6 +132,7 @@ Accepts the following options: + :key-fn - function that will be applied to each key :pretty - true if the JSON should be pretty-printed :escape-non-ascii - true if non-ASCII characters should be escaped with \\u :stream? - true to create JSON body as stream rather than string" diff --git a/test/ring/middleware/test/json.clj b/test/ring/middleware/test/json.clj index 80c9dc4..b5490a3 100644 --- a/test/ring/middleware/test/json.clj +++ b/test/ring/middleware/test/json.clj @@ -17,7 +17,7 @@ :body (string-input-stream "{\"foo\": \"bar\"}")} response (handler request)] (is (= {"foo" "bar"} (:body response))))) - + (testing "custom json body" (let [request {:headers {"content-type" "application/vnd.foobar+json; charset=UTF-8"} :body (string-input-stream "{\"foo\": \"bar\"}")} @@ -45,6 +45,13 @@ response (handler request)] (is (= {"foo" "你好"} (:body response)))))) + (let [handler (wrap-json-body identity {:key-fn (fn [k] (.toUpperCase (name k)))})] + (testing "key-fn" + (let [request {:headers {"content-type" "application/json; charset=UTF-8"} + :body (string-input-stream "{\"foo\": \"bar\"}")} + response (handler request)] + (is (= {"FOO" "bar"} (:body response)))))) + (let [handler (wrap-json-body identity {:keywords? true})] (testing "keyword keys" (let [request {:headers {"content-type" "application/json"} @@ -146,7 +153,7 @@ (is (= (get-in @response [:body :bigdecimals]) true))))))) (deftest test-json-params - (let [handler (wrap-json-params identity)] + (let [handler (wrap-json-params identity)] (testing "xml body" (let [request {:headers {"content-type" "application/xml"} :body (string-input-stream "") @@ -164,6 +171,15 @@ (is (= {"id" 3, "foo" "bar"} (:params response))) (is (= {"foo" "bar"} (:json-params response))))) + (testing "key-fn" + (let [request {:headers {"content-type" "application/json; charset=UTF-8"} + :body (string-input-stream "{\"foo\": \"bar\"}") + :params {"id" 3}} + handler (wrap-json-params identity {:key-fn (fn [k] (.toUpperCase (name k)))}) + response (handler request)] + (is (= {"id" 3, "FOO" "bar"} (:params response))) + (is (= {"FOO" "bar"} (:json-params response))))) + (testing "json body with bigdecimals" (let [handler (wrap-json-params identity {:bigdecimals? true}) request {:headers {"content-type" "application/json; charset=UTF-8"} @@ -313,6 +329,13 @@ (is (= (get-in response [:headers "Content-Type"]) "application/json; charset=utf-8")) (is (= (:body response) "[\"foo\",\"bar\"]")))) + (testing "key-fn" + (let [handler (constantly {:status 200 :headers {} :body {:foo "bar" :baz "quz"}}) + response ((wrap-json-response handler {:key-fn (fn [k] (.toUpperCase (name k)))}) {})] + (is (= (get-in response [:headers "Content-Type"]) "application/json; charset=utf-8")) + (is (or (= "{\"FOO\":\"bar\",\"BAZ\":\"quz\"}" (:body response)) + (= "{\"BAZ\":\"quz\",\"FOO\":\"bar\"}" (:body response)))))) + (testing "list body" (let [handler (constantly {:status 200 :headers {} :body '(:foo :bar)}) response ((wrap-json-response handler) {})]