18

I have a URL like this:

http%3A%2F%2Fexample.com%3Fa%3D1%26b%3D2%26c%3D3

I parsed it with hyper::Url::parse and fetch the query string:

let parsed_url = hyper::Url::parse(&u).unwrap();
let query_string = parsed_url.query();

But it gives me the query as a string. I want to get the query string as HashMap. something like this:

// some code to convert query string to HashMap
hash_query.get(&"a"); // eq to 1
hash_query.get(&"b"); // eq to 2
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Saeed M.
  • 2,216
  • 4
  • 23
  • 47
  • 1
    Should the input be `http://example.com?a=1&b=2&c=3` instead? It's not a valid URL when it's encoded like that. – Lambda Fairy Apr 07 '17 at 08:40
  • [`percent_decode`](https://docs.rs/percent-encoding/2.1.0/percent_encoding/fn.percent_decode.html) lib works for fully encoded URLs like Saeed's: `http%3A%2F%2Fexample.com%3Fa%3D1%26b%3D2%26c%3D3` – Mikeumus Aug 16 '19 at 00:40

3 Answers3

26

There are a few steps involved:

  • The .query_pairs() method will give you an iterator over pairs of Cow<str>.

  • Calling .into_owned() on that will give you an iterator over String pairs instead.

  • This is an iterator of (String, String), which is exactly the right shape to .collect() into a HashMap<String, String>.

Putting it together:

use std::collections::HashMap;
let parsed_url = Url::parse("http://example.com/?a=1&b=2&c=3").unwrap();
let hash_query: HashMap<_, _> = parsed_url.query_pairs().into_owned().collect();
assert_eq!(hash_query.get("a"), "1");

Note that you need a type annotation on the hash_query—since .collect() is overloaded, you have to tell the compiler which collection type you want.

If you need to handle repeated or duplicate keys, try the multimap crate:

use multimap::MultiMap;
let parsed_url = Url::parse("http://example.com/?a=1&a=2&a=3").unwrap();
let hash_query: MultiMap<_, _> = parsed_url.query_pairs().into_owned().collect();
assert_eq!(hash_query.get_vec("a"), Some(&vec!["1", "2", "3"]));
Lambda Fairy
  • 13,814
  • 7
  • 42
  • 68
  • 5
    Be warned that this overwrites duplicate keys. Many web frameworks take advantage of the fact that you can submit the same key multiple times. – Shepmaster Apr 07 '17 at 13:38
  • 11
    Note that if you just have a query string, not a full URL, then you can use [`url::form_urlencoded::parse()`](https://docs.rs/url/1.4.0/url/form_urlencoded/fn.parse.html) instead. – Lambda Fairy Apr 09 '17 at 01:16
  • 1
    Where is the ``HashMap`` type coming from? Edit: Neverming, it's ``::std::collections::HashMap``. – Philipp Ludwig Apr 30 '18 at 13:20
  • 1
    is there a way to parse repeated/duplicated keys as an array? – tworec Jul 04 '18 at 16:34
  • 2
    @tworec to preserve repeated keys, change the `HashMap<_, _>` to `Vec<_>` – Lambda Fairy Jul 04 '18 at 21:30
2

The other answer is good, but I feel that this is something that should be more straightforward, so I wrapped it in a function:

use {
   std::collections::HashMap,
   url::Url
};

fn query(u: Url) -> HashMap<String, String> {
   u.query_pairs().into_owned().collect()
}

fn main() -> Result<(), url::ParseError> {
   let u = Url::parse("http://stackoverflow.com?month=May&day=Friday")?;
   let q = query(u);
   println!("{:?}", q);
   Ok(())
}

Alternatively, I found another crate that does this for you:

use auris::URI;

fn main() -> Result<(), auris::ParseError> {
   let s = "http://stackoverflow.com?month=May&day=Friday";
   let u: URI<String> = s.parse()?;
   println!("{:?}", u.qs); // Some({"month": "May", "day": "Friday"})
   Ok(())
}

https://docs.rs/auris

Zombo
  • 1
  • 62
  • 391
  • 407
  • 2
    Regarding `auris`, I'm a bit suspicious of a crate that has no repository and no dependents. It's probably fine but I'd rather stick with the more reputable package. – Lambda Fairy Mar 29 '21 at 06:37
  • @LambdaFairy here is the repo: https://github.com/bluemoon/auris – ino Aug 14 '23 at 06:29
0

Here is a solution for parsing an already-decoded query string from &str into HashMap<String, String> using only the Rust standard library, with no external crates required:

use std::collections::HashMap;

fn parse_query(query: &str) -> HashMap<String, String> {
    query
        .split('&')
        .filter_map(|s| {
            s.split_once('=')
                .and_then(|t| Some((t.0.to_owned(), t.1.to_owned())))
        })
        .collect()
}