19
votes

I'm serializing a HashMap with serde, like so:

#[derive(Serialize, Deserialize)]
struct MyStruct {
    map: HashMap<String, String>
}

HashMap's key order is unspecified, and since the hashing is randomized (see documentation), the keys actually end up coming out in different order between identical runs.

I'd like my HashMap to be serialized in sorted (e.g. alphabetical) key order, so that the serialization is deterministic.

I could use a BTreeMap instead of a HashMap to achieve this, as BTreeMap::keys() returns its keys in sorted order, but I'd rather not change my data structure just to accommodate the serialization logic.

How do I tell serde to sort the HashMap keys before serializing?

1
Note: you may be interested in bluss' OrderMap, a HashMap whose iteration order solely depends on the order in which elements have been inserted and removed. - Matthieu M.

1 Answers

19
votes

Use the serialize_with field attribute:

use serde::{Deserialize, Serialize, Serializer}; // 1.0.106
use serde_json; // 1.0.52
use std::collections::{BTreeMap, HashMap};

#[derive(Serialize, Deserialize, Default)]
struct MyStruct {
    #[serde(serialize_with = "ordered_map")]
    map: HashMap<String, String>,
}

fn ordered_map<S>(value: &HashMap<String, String>, serializer: S) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    let ordered: BTreeMap<_, _> = value.iter().collect();
    ordered.serialize(serializer)
}

fn main() {
    let mut m = MyStruct::default();
    m.map.insert("gamma".into(), "3".into());
    m.map.insert("alpha".into(), "1".into());
    m.map.insert("beta".into(), "2".into());

    println!("{}", serde_json::to_string_pretty(&m).unwrap());
}

Here, I've chosen to just rebuild an entire BTreeMap from the HashMap and then reuse the existing serialization implementation.

{
  "map": {
    "alpha": "1",
    "beta": "2",
    "gamma": "3"
  }
}