River Tam
River Tam

Reputation: 3216

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?

Upvotes: 3

Views: 3590

Answers (3)

Mad Physicist
Mad Physicist

Reputation: 114230

Serde has nifty from and into annotations that you can use to specify an alternate type to use for serialization. As long as Grid implements Clone (which should be trivial and harmless), and you implement the appropriate conversions, you are good to go. The nice thing about this approach is that you can serialize Grid exactly as you want directly, with a minimum of custom types:

#[derive(Clone, Deserialize, Serialize)]
#[serde(from="Vec<Vec<Cell>>", into="Vec<Vec<Cell>>"]
struct Grid {
  cell: Cell,
  width: usize,
  height: usize,
}

#[derive(Clone, Deserialize, Serialize)]
struct Cell {
  display: String,
  solution: String,
}

impl From<Vec<Vec<Cell>>> for Grid {
    fn from(value: Vec<Vec<Cell>>) -> Self {
        // Optionally check that all the values are identical here
        Self {
            cell: value[0][0],
            width: value[0].len(),
            height: value.len(),
        }
    }
}

impl Into<Vec<Vec<Cell>>> for Grid {
    fn into(self) -> Vec<Vec<Grid>> {
        let row = vec![self.cell; self.width];
        vec![row; self.height]
    }
}

If you run into issues with traits on externally defined objects, you can always make a custom type for serialization. That shouldn't happen with the code above, but I prefer to implement From rather than into most of the time, which you can't do for Vec, since you own neither Vec nor From.

That being said, you can't get away from both redundant data structures and custom serializers. You need to specify a way to map your Grid to JSON: you can choose to turn it into a common structure that Serde understands, or provide the same information via custom instructions, but you can't escape it altogether.

Upvotes: 1

Joe_Jingyu
Joe_Jingyu

Reputation: 1269

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

Upvotes: 1

Jmb
Jmb

Reputation: 23244

You need a couple of helper structs to represent a row and a cell:

use serde::*; // 1.0.130
use serde::ser::*;

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

impl Serialize for Grid {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        let mut rows = serializer.serialize_seq (Some (self.height))?;
        for idx in 0..self.height {
            rows.serialize_element (&Row { grid: self, start_idx: idx * self.width })?;
        }
        rows.end()
    }
}

struct Row<'a> {
    grid: &'a Grid,
    start_idx: usize,
}

impl<'a> Serialize for Row<'a> {
    fn serialize<S: Serializer> (&self, serializer: S) -> Result<S::Ok, S::Error> {
        let mut row = serializer.serialize_seq (Some (self.grid.width))?;
        for idx in 0..self.grid.width {
            row.serialize_element (&Cell { grid: self.grid, idx: self.start_idx + idx })?;
        }
        row.end()
    }
}

struct Cell<'a> {
    grid: &'a Grid,
    idx: usize,
}

impl<'a> Serialize for Cell<'a> {
    fn serialize<S: Serializer> (&self, serializer: S) -> Result<S::Ok, S::Error> {
        let mut cell = serializer.serialize_map (Some (2))?;
        cell.serialize_entry ("display", &self.grid.display.chars().nth (self.idx).unwrap())?;
        cell.serialize_entry ("solution", &self.grid.solution.chars().nth (self.idx).unwrap())?;
        cell.end()
    }
}

Playground

Upvotes: 2

Related Questions