Serialising a custom Struct

Hi, I would like to serialise a Struct (with serde, serde_json and serde_pickle), I believed it should be simple, but I can't make it work. Would be grateful for help.

#[derive(Serialize, Deserialize, Debug)]
pub struct Moves {
    moves: HashMap<(usize, usize), f32>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct QTable {
    qtable: HashMap<String, Moves>,
}

Can you provide an example with an example value, the current serialization, and how it differs from your expectation?

1 Like

I create a new Struct with these functions, pickle file is created but json file is not. I cannot deserialise it. I am sorry it is probably messy, I am just learning Rust and that is how it is now.

fn new(m: Vec<(usize, usize)>) -> Moves {
        let mut rng = thread_rng();
        let moves: HashMap<(usize, usize), f32> = m
            .into_iter()
            .map(|(x, y)| ((x, y), rng.gen_range(-0.15f32..0.15f32)))
            .collect();
        Moves { moves }
    }

fn new() -> Self {
        QTable {
            qtable: HashMap::with_capacity(5500),
        }
    }

fn is_q_table_to_disk_working() {
        let mut q = QTable::new();
        let mut test_board = Board::new();
        test_board.current_state.state[[1, 1]] = 'X';
        test_board.current_state.state[[2, 2]] = '0';
        let q_key = test_board.current_state.to_state_key();
        let t = q
            .entry(q_key)
            .or_insert(Moves::new(test_board.available_moves()));
        let path = Path::new(".");
        let _ = q_table_to_disk(&path, &q);
    }
fn q_table_to_disk(path: &std::path::Path, q: &QTable) -> Result<(), anyhow::Error> {
    let dt = Local::now();
    let timestamp: i64 = dt.timestamp();
    let today = dt.date_naive();
    let filename = "qtable-".to_owned() + (&today.to_string());
    let filename_json = "qtable-".to_owned() + &today.to_string() + r#".json"#;
    let q_json: PathBuf = [path, &Path::new(&filename_json)].iter().collect();
    let mut file = File::create(&filename)?;
    let mut file_json = File::create(&q_json)?;
    let data_json = serde_json::to_string(&q).unwrap();
    file_json.write_all(&data_json.as_bytes())?;
    serde_pickle::to_writer(&mut file, q, serde_pickle::SerOptions::new())?;
    Ok(())
}

I believe it should work with 1-2 lines of code, but cannot figure it out.

JSON requires that your objects have string keys. serde_json can also convert numbers to/from strings, but you have to decide what you want to do with the (usize, usize) here, or change the whole HashMap serialization. If you describe what you want the JSON to be I can help more. There are a bunch of ways to deal with this situation. You can look at https://docs.rs/serde_with/latest/serde_with/ for inspirations.

2 Likes

Thank you, I didn't know that JSON requires string keys. I am trying to implement a RL agent for tic tac toe as a learning project. I just want to save the Q table that the agent learned to disc, in JSON file. I thought it is possible to do it automatically or with custom implementation of Serialize trait. Thank you for the crate suggestion, I will read it.

I thought is it possible to serialize (usize, usize) as String to be compatible with JSON format?

You can't tell serde to serialize (usize, usize), but you can tell it how to serialize your own type by implementing Serialize yourself. To deserialize, you can implement FromStr and use serde_with::DeserializeFromStr to do it for you (I don't know how to implement deserialize :melting_face:, although the answer is presumably in serde.rs)

use serde::{Deserialize, Serialize, Serializer};
use serde_with::DeserializeFromStr;
use std::{collections::HashMap, str::FromStr};

#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct Moves {
    moves: HashMap<Cell, f32>,
}

#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct QTable {
    qtable: HashMap<String, Moves>,
}

#[derive(PartialEq, Eq, Hash, Debug, DeserializeFromStr)]
struct Cell(usize, usize);

impl Serialize for Cell {
    fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&format!("{},{}", self.0, self.1))
    }
}

impl FromStr for Cell {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let (first, second) = s.split_once(',').ok_or_else(|| "No comma?".to_owned())?;
        Ok(Self(
            first.parse::<usize>().map_err(|err| err.to_string())?,
            second.parse::<usize>().map_err(|err| err.to_string())?,
        ))
    }
}
1 Like

Thank you very much! You gave me the crucial piece of information, somehow I didn't pay attention that the key should be String. I will try to learn how to do it. From what I read it is possible to hint to what kind of struct the JSON file should be deserialized.
Thank you!

1 Like

It turned out to be very easy! Rust is such a wonderful language!

impl Serialize for Moves {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut map = serializer.serialize_map(Some(self.len()))?;
        for (k, v) in self.deref() {
            let key_str = format!("({}, {})", k.0, k.1);
            map.serialize_entry(&key_str, &v)?;
        }
        map.end()
    }
}
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.