Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to sort HashMap keys when serializing with serde?

Tags:

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?

like image 381
Jo Liss Avatar asked Mar 10 '17 16:03

Jo Liss


1 Answers

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"
  }
}
like image 129
Shepmaster Avatar answered Oct 02 '22 16:10

Shepmaster