0

I would like to construct a string slice that consists of the map's keys and values, concatenated and split by a character, say &. I've managed to iterate over the map and push key=value, however I don't know how to split the pairs by &. I can add it in the format! macro but then I have to .pop the last one which is ugly.

Note that I have more than 4 keys in my map, so this should ideally be done iteratively.

use std::collections::BTreeMap;

fn main() {
    let mut map: BTreeMap<&str, &str> = BTreeMap::new();
    map.insert("key1", "value1");
    map.insert("key2", "value2");
    map.insert("key3", "value3");
    map.insert("key4", "value4");
    
    let mut result = String::new();
    for (key, value) in &map {
        let kv = format!("{}={}", key, value);
        result.push_str(&kv);
    }
    println!("{}", result);

    let wanted_result = format!("key1=value1&key2=value2&key3=value3&key4=value4");
}
user270199
  • 905
  • 1
  • 6
  • 12
  • 3
    Does this answer your question? [What is the equivalent of the join operator over a vector of Strings?](https://stackoverflow.com/questions/28311868/what-is-the-equivalent-of-the-join-operator-over-a-vector-of-strings) – Chayim Friedman Feb 24 '22 at 00:32
  • Sounds like you may want join from the itertools crate, which is like what @ChayimFriedman said but works on an iterator. Then you could do something like `map.iter().map((key, value) => format!("{}={}", key, value)).join("&")`. – PitaJ Feb 24 '22 at 21:56
  • If what you really want to do is serialize a map to a query string, you may want to use something like [`serde_qs`](https://docs.rs/serde_qs/latest/serde_qs/index.html) – PitaJ Feb 24 '22 at 22:01
  • @PitaJ serde_qs was exactly what I was after. If you write this as an answer, then I'll accept it. Thanks! – user270199 Feb 24 '22 at 22:28

1 Answers1

1

If you're looking specifically to serialize a map into a query string, you probably want to use something like serde_qs.

If you're trying to perform this serialization manually, an efficient way (minimizing allocations) would be to use Iterator::fold like so:

use std::collections::BTreeMap;

fn main() {
    let mut map: BTreeMap<&str, &str> = BTreeMap::new();
    map.insert("key1", "value1");
    map.insert("key2", "value2");
    map.insert("key3", "value3");
    map.insert("key4", "value4");
    
    let result: String = map
        .iter()
        .fold(String::new(), |mut res, (key, val)| {
            if res.len() > 0 {
                res.push('&');
            }
            res.push_str(key);
            res.push('=');
            res.push_str(val);
            res
        });

    let wanted_result = "key1=value1&key2=value2&key3=value3&key4=value4";
    assert_eq!(&result, wanted_result);
}

playground

That may be premature optimization, and you may want a prettier way of doing so. In that case, you could use Itertools::join instead:

use std::collections::BTreeMap;
use itertools::Itertools;

fn main() {
    let mut map: BTreeMap<&str, &str> = BTreeMap::new();
    map.insert("key1", "value1");
    map.insert("key2", "value2");
    map.insert("key3", "value3");
    map.insert("key4", "value4");
    
    let result: String = map
        .iter()
        .map(|(key, val)| format!("{}={}", key, val))
        .join("&");

    let wanted_result = "key1=value1&key2=value2&key3=value3&key4=value4";
    assert_eq!(&result, wanted_result);
}

playground

PitaJ
  • 12,969
  • 6
  • 36
  • 55