Skip to content

Instantly share code, notes, and snippets.

What would you like to do?

Parse query parameters

URLs can have optional query parameters. They are the key-value pairs that follow the path.

Write a function that takes a URL (represented as a string) and parses out the query parameters into a hashmap.


  • The query string is the string containing the query parameters. It appears after the ? and before a #. Ex:
  • The keys and values are URL Encoded. You can use to decode them.
  • The key-value pairs are separated by &. Ex: a=1&b=2
  • Each key-value pair contains the key, followed by =, followed by the value. Ex: a=1


Query parameters can contain duplicate keys. Handle them gracefully.

(defn query-string [url]
(-> url
(clojure.string/split #"\?")
(defn key-value-strings [qs]
(clojure.string/split qs #"&"))
(defn key-value-pair [kvs]
(mapv #( %) (clojure.string/split kvs #"=")))
(defn conj-multi [mp [k v]]
(string? (get mp k))
(update mp k #(vector % v))
(vector? (get mp k))
(update mp k conj v)
(assoc mp k v)))
(defn parse-query-params [url]
(let [kvs (some->> url
(mapv key-value-pair))]
(reduce conj-multi {} kvs)))
(defn- decode [val]
( val "UTF-8"))
(decode "q=%23%5E%21%26*")
;; => "q=#^!&*"
(defn- decode-param-string
"Converts a string like 'a=b' to a vector ot two strings [a b]"
(let [name+val (str/split param-string #"=")]
(mapv decode name+val)))
(defn query-params
"Expects url as string and return a map with query string parameters."
(let [query-params-strings (some-> url
(str/split #"\?")
(str/split #"&"))
params (into {} (map decode-param-string query-params-strings))]
(deftest test-query-params
(testing "no params"
(is (= {} (query-params "")))
(is (= {} (query-params ""))))
(testing "simple params"
(is (= {"a" "b", "z" "y"} (query-params ""))))
(testing "params with special chars"
(is (= {"q" "#^!&*", "source" "hp"} (query-params "*&source=hp"))))
(testing "duplicate param"
(is (= {"a" "c", "z" "y"} (query-params ""))))
Copy link

KingCode commented May 7, 2020

With handling of duplicate and multiple-valued parameters and single pass-through, ideal for those million-parameter URLs ;)

(defn decode [s]
  ( s))

(defn query-params [url]
  (let [params-str (second (clojure.string/split url #"\?"))
        attrs-xf (comp (partition-by #(= \& %))
                       (remove #{[\&]})
                       (map #(apply str %))
                       (map #(clojure.string/split % #"="))
                       (map #(mapv decode %)))]
    (->> (sequence attrs-xf params-str) 
         (reduce (fn [m [k v]]
                   (update m k (fn [v x] 
                                   (and (string? v) (= v x)) v
                                   (string? v) (conj [] v x)
                                   (nil? v) x 
                                   :else (conj v x))) v))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment