Reputation: 6399
I have come back to clojure after moderately dabbling with it about 10+ years ago, and so I might be doing something silly here.
I am trying to write a simple API with compojure
and ring
server, and right now I've isolated my problem to just a few lines. I have a route and a handler, and I've wrapped my handler with wrap-json-body
as is suggested in ring-json
documentation.
My handler.clj
is like so:
(defroutes app-routes
(PUT "/item/:id" {{id :id} :params body :body} (str id ", " body))
(route/not-found "Not Found"))
(def app
(-> app-routes
(middleware/wrap-json-body)
(middleware/wrap-json-response)))
This should be simple enough, and I am able to return clojure data as json OK. Problem is when I'm trying to read PUT
request body json.
$ curl -XPUT -H "Content-type: application/json" -d '{ "id": 32, "name": "pad" }' 'localhost:3001/item/5'
5, {"id" 32, "name" "pad"}
I would expect body
to be populated with {:id 32 :name "pad"}
Here's the whole request object:
; (PUT "/item/:id" body (str id body))
$ curl -XPUT -H "Content-type: application/json" -d '{ "id": 32, "name": "pad" }'
{:ssl-client-cert nil, :protocol "HTTP/1.1", :remote-addr "0:0:0:0:0:0:0:1", :params {:id "5"}, :route-params {:id "5"}, :headers {"user-agent" "curl/7.58.0", "host" "localhost:3001", "accept" "*/*", "content-length" "27", "content-type" "application/json"}, :server-port 3001, :content-length 27, :compojure/route [:put "/item/:id"], :content-type "application/json", :character-encoding "UTF-8", :uri "/item/5", :server-name "localhost", :query-string nil, :body {"id" 32, "name" "pad"}, :scheme :http, :request-method :put}
I've tweaked and changed it to a few things but I can't seem to get :body
to be populated with keyword-ed clojure data.
What am I doing wrong please?
ps: If you'd like to see a working example of this problem, I've uploaded it to github
Upvotes: 5
Views: 972
Reputation: 573
There also exists a "wrap-keyword-params" ring middleware which is usually placed at the end of the middleware chain and it converts all the keys in :params gathered up to that point into keywords.
Where I work at, we usually use the following pattern:
(def app
(-> handler
wrap-keyword-params ;; <--- converts any string keys in the :params map to keywords
wrap-nested-params ;; <--- parses nested params
wrap-params ;; <--- parses query-params into :query-params and adds parsed request body props into :params
wrap-json-params ;; <--- parses body of a request and adds ":params" and ":json-params" keys to "request" argument (in the following format: {"key" "val"})
wrap-json-response) ;; <--- converts response with body that is a map or a vector to JSON)
Upvotes: 1
Reputation: 3528
Use the keywords?
parameter in wrap-json-body:
(middleware/wrap-json-body app-routes {:keywords? true})
Alternatively, since ring-clojure version 0.5.1, you can specify a custom key-fn
to convert the keys of maps:
(middleware/wrap-json-body app-routes {:key-fn keyword})
Upvotes: 6