Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to parse URL parameters in Clojure?

Tags:

clojure

If I have the request "size=3&mean=1&sd=3&type=pdf&distr=normal" what's the idiomatic way of writing the function (defn request->map [request] ...) that takes this request and returns a map {:size 3, :mean 1, :sd 3, :type pdf, :distr normal}

Here is my attempt (using clojure.walk and clojure.string):

(defn request-to-map
   [request]
   (keywordize-keys
      (apply hash-map
             (split request #"(&|=)"))))

I am interested in how others would solve this problem.

like image 653
ChrisR Avatar asked Jul 06 '11 04:07

ChrisR


5 Answers

Using form-decode and keywordize-keys:

(use 'ring.util.codec)
(use 'clojure.walk)

(keywordize-keys (form-decode "hello=world&foo=bar"))

{:foo "bar", :hello "world"}
like image 110
KendallB Avatar answered Nov 17 '22 18:11

KendallB


Assuming you want to parse HTTP request query parameters, why not use ring? ring.middleware.params contains what you want.

The function for parameter extraction goes like this:

(defn- parse-params
  "Parse parameters from a string into a map."
  [^String param-string encoding]
  (reduce
    (fn [param-map encoded-param]
      (if-let [[_ key val] (re-matches #"([^=]+)=(.*)" encoded-param)]
        (assoc-param param-map
          (codec/url-decode key encoding)
          (codec/url-decode (or val "") encoding))
         param-map))
    {}
    (string/split param-string #"&")))
like image 22
ordnungswidrig Avatar answered Nov 17 '22 18:11

ordnungswidrig


You can do this easily with a number of Java libraries. I'd be hesitant to try to roll my own parser unless I read the URI specs carefully and made sure I wasn't missing any edge cases (e.g. params appearing in the query twice with different values). This uses jetty-util:

(import '[org.eclipse.jetty.util UrlEncoded MultiMap])

(defn parse-query-string [query]
  (let [params (MultiMap.)]
    (UrlEncoded/decodeTo query params "UTF-8")
    (into {} params)))

user> (parse-query-string "size=3&mean=1&sd=3&type=pdf&distr=normal")
{"sd" "3", "mean" "1", "distr" "normal", "type" "pdf", "size" "3"}
like image 34
Brian Carper Avatar answered Nov 17 '22 18:11

Brian Carper


Can also use this library for both clojure and clojurescript: https://github.com/cemerick/url

user=> (-> "a=1&b=2&c=3" cemerick.url/query->map clojure.walk/keywordize-keys)
{:a "1", :b "2", :c "3"}
like image 25
bluegray Avatar answered Nov 17 '22 19:11

bluegray


Yours looks fine. I tend to overuse regexes, so I would have solved it as

(defn request-to-keywords [req]
  (into {} (for [[_ k v] (re-seq #"([^&=]+)=([^&]+)" req)]
    [(keyword k) v])))

(request-to-keywords "size=1&test=3NA=G")

{:size "1", :test "3NA=G"}

Edit: try to stay away from clojure.walk though. I don't think it's officially deprecated, but it's not very well maintained. (I use it plenty too, though, so don't feel too bad).

like image 26
amalloy Avatar answered Nov 17 '22 19:11

amalloy