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.
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(())
}
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.
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.
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 , 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())?,
))
}
}
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!