url-query-string-round-trip

Clojure
(require '[clojure.string :as str])

(import '[java.net URLDecoder URLEncoder]
        '[java.nio.charset StandardCharsets])

(defn- encode [s] (URLEncoder/encode (str s) StandardCharsets/UTF_8))
(defn- decode [s] (URLDecoder/decode (str s) StandardCharsets/UTF_8))

(defn query-string
  "Builds a=1&b=two from a map, skipping nil values; keys may be keywords."
  [params]
  (->> params (remove (comp nil? val))
       (map (fn [[k v]] (str (encode (name k)) "=" (encode v))))
       (str/join "&")))

(defn parse-query
  "Parses a query string into {key [values]}, URL-decoding both sides."
  [qs]
  (reduce (fn [m pair]
            (let [[k v] (str/split pair #"=" 2)]
              (update m (decode (or k "")) (fnil conj []) (decode (or v "")))))
          {} (remove str/blank? (str/split (or qs "") #"&"))))