Library to get a map out of the URL query-string?

How to get a map of key-value pairs out of the query string of a url? We want to avoid string wrangling for what is surely a very common use-case. If the given way is utilizing a tiny JS function/lib and then converting it to a ClojureScript map, that’s fine. Example:

https ://example.com?cat=meow&dog=woof
becomes
{“cat” “meow” “dog” “woof”}

Bonus if it were using Google Closure, or core js.

You should be able to use URLSearchParams for this, something like

Object.fromEntries(new URLSearchParams(location.search)))
3 Likes

Works for me based on the above answer. If anyone can improve this one liner please do. Map from string

(-> "https://x.site?a=1&b=2"
    js/URL.
    .-searchParams
    js/Object.fromEntries
    (js->clj :keywordize-keys true))
;;=> {:a "1", :b "2"}
1 Like

To add to the answers above - note that a query string can have multiple parameters with the same name. A naive “mappification” will ignore all but one.

1 Like

So that’s legal, but has it defined behavior?

A query string has no behavior. :slight_smile: But the way to interpret repeating parameter names is up to the implementation. More than that - the whole query string doesn’t have a standardized format. I’m too lazy to look through the relevant RFCs, but this is what Wikipedia says:

The exact structure of the query string is not standardized. Methods used to parse the query string may differ between websites.

1 Like

good catch.

URLSearchParams.forEach will iterate across all values:

function arrayify(x){
 return Array.isArray(x) ? x : ((null == x) ? [] : [x]);
}
 
let out = {};
new URL("https://x.site?a=1&b=2&b=3").searchParams.forEach(function (v,k){
  let curr = out[k];
  if(curr){
    let next = arrayify(curr);
    next.push(v);
    out[k] = next;
  }
  else{
    out[k] = v;
  }
});

console.log(out);
=> {"a": "1", "b": ["2", "3"]}

This uses only String.split, overwriting duplicate keys.

function params_from_url(url){
  let arr = url.split("?");
  let search = arr[1];
  let params = {};
  if(search){
    for(let pair of search.split("&")){
      let [key,val] = pair.split("=");
      params[key] = val;
    };
  }
  return params;
 }

params_from_url("https://x.site?a=1&b=2&b=3"
=> {a: "1", b: "3"}  

this function can be readily converted an a reduce call for creating clj datastructures, optionally adding in the logic if single key/multiple values are needed.

Lambda Island’s URI lib has query-string->map. It’s CLJC.

1 Like

Didn’t know about lambda islands’ lib, so thanks for that @dave.liepmann :slight_smile:

Out of curiosity I had to test how little I can get away with by using URLSearchParams:

(reduce
 (fn [m [k v]]
   (update m (keyword k) (fn [cur]
                           (cond
                             (nil? cur) v
                             (vector? cur) (conj cur v)
                             :else [cur v]))))
 {}
 (js/Array.from (js/URLSearchParams. "?a=1&b=2&b=3&c&c=4%205")))
=> {:a "1", :b ["2" "3"], :c ["" "4 5"]}
2 Likes

Nice. Array.from is so much simple than using forEach. It’s great that URLSearchParams supports iteration as both an array and object. The class also does auto URLDecoding (which won’t with my example).

Also… this probably show how long I haven’t been using cljs… but when did map and reduce start working with native arrays?


I guess each implementation has their benefits, ie

  • lambda island is jvm/js compatible
  • URLSearchParams is simplest for js
  • String.split solution can be ported/transpiled pretty easily to other langs such as lua/python