// 761. Custom Serialization for Complex Types
// Enums with payloads, Option<T>, Vec<T> โ all hand-rolled
use std::fmt::Write as FmtWrite;
// โโ Error โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
#[derive(Debug)]
pub enum SerError {
Eof,
BadTag(String),
ParseError(String),
}
// โโ Traits โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
pub trait Serialize {
fn serialize(&self, out: &mut String);
}
pub trait Deserialize: Sized {
fn deserialize(s: &str) -> Result<(Self, &str), SerError>;
}
// โโ Primitive impls โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
impl Serialize for f64 {
fn serialize(&self, out: &mut String) { write!(out, "{}", self).unwrap(); }
}
impl Serialize for u32 {
fn serialize(&self, out: &mut String) { write!(out, "{}", self).unwrap(); }
}
impl Serialize for String {
fn serialize(&self, out: &mut String) {
write!(out, "{}:{}", self.len(), self).unwrap();
}
}
impl Deserialize for f64 {
fn deserialize(s: &str) -> Result<(f64, &str), SerError> {
let end = s.find('|').or_else(|| s.find('\n')).unwrap_or(s.len());
let v = s[..end].parse::<f64>().map_err(|e| SerError::ParseError(e.to_string()))?;
Ok((v, &s[end..]))
}
}
impl Deserialize for String {
fn deserialize(s: &str) -> Result<(String, &str), SerError> {
let colon = s.find(':').ok_or(SerError::Eof)?;
let len: usize = s[..colon].parse().map_err(|e| SerError::ParseError(format!("{e}")))?;
let rest = &s[colon + 1..];
if rest.len() < len { return Err(SerError::Eof); }
Ok((rest[..len].to_string(), &rest[len..]))
}
}
// โโ Domain enum โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
#[derive(Debug, PartialEq)]
pub enum Shape {
Circle(f64),
Rectangle { width: f64, height: f64 },
Point,
}
impl Serialize for Shape {
fn serialize(&self, out: &mut String) {
match self {
Shape::Circle(r) => {
out.push_str("C|");
r.serialize(out);
}
Shape::Rectangle { width, height } => {
out.push_str("R|");
width.serialize(out);
out.push('|');
height.serialize(out);
}
Shape::Point => out.push('P'),
}
}
}
impl Deserialize for Shape {
fn deserialize(s: &str) -> Result<(Shape, &str), SerError> {
match s.chars().next().ok_or(SerError::Eof)? {
'C' => {
let rest = &s[2..]; // skip "C|"
let (r, rest) = f64::deserialize(rest)?;
Ok((Shape::Circle(r), rest))
}
'R' => {
let rest = &s[2..];
let (w, rest) = f64::deserialize(rest)?;
let rest = rest.trim_start_matches('|');
let (h, rest) = f64::deserialize(rest)?;
Ok((Shape::Rectangle { width: w, height: h }, rest))
}
'P' => Ok((Shape::Point, &s[1..])),
c => Err(SerError::BadTag(c.to_string())),
}
}
}
// โโ Option<T> โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
impl<T: Serialize> Serialize for Option<T> {
fn serialize(&self, out: &mut String) {
match self {
None => out.push('N'),
Some(v) => { out.push('S'); v.serialize(out); }
}
}
}
// โโ Vec<Shape> round-trip โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
fn serialize_shapes(shapes: &[Shape]) -> String {
let mut out = String::new();
write!(out, "{}\n", shapes.len()).unwrap();
for s in shapes {
s.serialize(&mut out);
out.push('\n');
}
out
}
fn deserialize_shapes(s: &str) -> Result<Vec<Shape>, SerError> {
let mut lines = s.lines();
let count: usize = lines.next().ok_or(SerError::Eof)?
.parse().map_err(|e| SerError::ParseError(format!("{e}")))?;
let mut shapes = Vec::with_capacity(count);
for line in lines.take(count) {
let (shape, _) = Shape::deserialize(line)?;
shapes.push(shape);
}
Ok(shapes)
}
fn main() {
let shapes = vec![
Shape::Circle(3.14),
Shape::Rectangle { width: 2.0, height: 5.0 },
Shape::Point,
];
let encoded = serialize_shapes(&shapes);
println!("Encoded:\n{encoded}");
let decoded = deserialize_shapes(&encoded).expect("decode failed");
println!("Decoded: {decoded:?}");
// Option demo
let maybe: Option<u32> = Some(42);
let mut buf = String::new();
maybe.serialize(&mut buf);
println!("Option<42> = {buf}");
let none: Option<u32> = None;
let mut buf2 = String::new();
none.serialize(&mut buf2);
println!("Option<None> = {buf2}");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn circle_round_trip() {
let s = Shape::Circle(2.718);
let mut buf = String::new();
s.serialize(&mut buf);
let (decoded, _) = Shape::deserialize(&buf).unwrap();
assert_eq!(decoded, Shape::Circle(2.718));
}
#[test]
fn rect_round_trip() {
let s = Shape::Rectangle { width: 3.0, height: 4.0 };
let mut buf = String::new();
s.serialize(&mut buf);
let (decoded, _) = Shape::deserialize(&buf).unwrap();
assert_eq!(decoded, s);
}
#[test]
fn point_round_trip() {
let s = Shape::Point;
let mut buf = String::new();
s.serialize(&mut buf);
let (decoded, _) = Shape::deserialize(&buf).unwrap();
assert_eq!(decoded, Shape::Point);
}
#[test]
fn vec_shapes_round_trip() {
let shapes = vec![Shape::Circle(1.0), Shape::Point];
let enc = serialize_shapes(&shapes);
let dec = deserialize_shapes(&enc).unwrap();
assert_eq!(shapes, dec);
}
}
(* Custom serialization for complex types in OCaml
Handling sum types (variants), options, and lists *)
type shape =
| Circle of float
| Rectangle of float * float
| Point
(* Serialize a shape to a string representation *)
let serialize_shape = function
| Circle r -> Printf.sprintf "circle|r=%.6g" r
| Rectangle (w, h) -> Printf.sprintf "rect|w=%.6g|h=%.6g" w h
| Point -> "point"
let deserialize_shape s =
match String.split_on_char '|' s with
| ["circle"; rv] ->
(match String.split_on_char '=' rv with
| ["r"; v] -> (try Some (Circle (float_of_string v)) with _ -> None)
| _ -> None)
| ["rect"; wv; hv] ->
(match String.split_on_char '=' wv, String.split_on_char '=' hv with
| ["w"; w], ["h"; h] ->
(try Some (Rectangle (float_of_string w, float_of_string h)) with _ -> None)
| _ -> None)
| ["point"] -> Some Point
| _ -> None
(* Serialize a list of shapes *)
let serialize_shapes shapes =
let parts = List.map serialize_shape shapes in
(* length-prefix the list *)
Printf.sprintf "%d\n%s" (List.length parts) (String.concat "\n" parts)
let () =
let shapes = [Circle 3.14; Rectangle (2.0, 5.0); Point; Circle 1.0] in
let s = serialize_shapes shapes in
Printf.printf "Serialized:\n%s\n\n" s;
(* deserialize each line after the count *)
let lines = String.split_on_char '\n' s in
match lines with
| [] -> ()
| _ :: rest ->
List.iteri (fun i line ->
if line <> "" then
match deserialize_shape line with
| Some sh -> Printf.printf "Shape %d: %s\n" i (serialize_shape sh)
| None -> Printf.printf "Shape %d: FAILED\n" i
) rest