Search code examples
rustserde

Serialize a nested array of objects using serde


From a struct describing a puzzle grid, I'd like to be able to serialize a nested array:

struct Grid {
  display: String,
  solution: String,
  width: usize,
  height: usize,
}

Assuming a struct of Grid { display: "????", solution: "1234", width: 2, height: 2 }, I'd like the output to look like the following (in JSON):

[
  [
    {
      "display": "?",
      "solution": "1"
    },
    {
      "display": "?",
      "solution": "2"
    }
  ],
  [
    {
      "display": "?",
      "solution": "3"
    },
    {
      "display": "?",
      "solution": "4"
    }
  ]
]

My initial draft implementation looks like this:

impl<'a> Serialize for Grid<'a> {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        let mut columns = serializer.serialize_seq(Some(self.height))?;
        for column_index in 0..self.height {
            let mut column = columns.serialize_seq(Some(self.width))?;
            for row_index in 0..self.width {
                let mut cell = column.serialize_map(Some(2))?;
                let cell_index = column_index * self.height + row_index;
                cell.serialize_entry("display", self.display.chars().nth(cell_index).unwrap())?;
                cell.serialize_entry("solution", self.solution.chars().nth(cell_index).unwrap())?;

                cell.end()?;
            }

            column.end()?;
        }

        columns.end()
    }
}

However, SerializeSeq does not expose another serialize_seq method for further nesting. How can I serialize a nested array out of one struct like this?


Solution

  • A helper struct Cell with display and solution as fields will make the serialization easier. Then, you can construct a list of Cells in a functional way with the iterator adapters including zip and map from the solution and display values of the grid. Then, use the chunks adapter to convert the one-dimension vector into a two-dimension vector by the row size. At last, use the macro json! to generate the json string.

    use serde::{Deserialize, Serialize};
    use serde_json::json;
    
    struct Grid {
      display: String,
      solution: String,
      width: usize,
      height: usize,
    }
    
    #[derive(Serialize, Deserialize)]
    struct Cell {
        display: char,
        solution: char,
    }
    
    fn main() {
        let grid = Grid { display: "????".to_string(), solution: "1234".to_string(), width: 2, height: 2 };
    
        let cells :Vec<Cell> = grid.display.chars().zip(grid.solution.chars()).map(|(a, b)| Cell {display: a, solution: b} ).collect();
        let rows : Vec<&[Cell]>= cells.chunks(grid.width).collect();
    
        print!("{:#}", json!(rows));
    }
    

    playground