Serde State Machine Serializer


#1

Something I don’t quite understand about serde: What’s the purpose for using a state machine for serialisation? Take the following snippet from the repo:

struct PointMapVisitor<'a> {
    value: &'a Point,
    state: u8,
}

impl<'a> serde::ser::MapVisitor for PointMapVisitor<'a> {
    fn visit<S>(&mut self, serializer: &mut S) -> Result<Option<()>, S::Error>
        where S: serde::Serializer
    {
        match self.state {
            0 => {
                self.state += 1;
                Ok(Some(try!(serializer.serialize_struct_elt("x", &self.value.x))))
            }
            1 => {
                self.state += 1;
                Ok(Some(try!(serializer.serialize_struct_elt("y", &self.value.y))))
            }
            _ => {
                Ok(None)
            }
        }
    }
}

What’s the benefit of this over something like this:

struct PointMapVisitor<'a> {
    value: &'a Point
}

impl<'a> serde::ser::MapVisitor for PointMapVisitor<'a> {
    fn visit<S>(&mut self, serializer: &mut S) -> Result<Option<()>, S::Error>
        where S: serde::Serializer
    {
        try!(serializer.serialize_struct_elt("x", &self.value.x));
        try!(serializer.serialize_struct_elt("y", &self.value.y));
        Ok(None)
    }
}

Is it a perf thing, something to do with optional/missing values?


#2

Actually you are asking about the Serializer not the Deserializer. Your second code differs from the first code (it won’t even compile). The above code allows you to serialize one field, and then call some code of your own. An alternative would be to use closures.

Note that e.g. json uses this to not produce a comma after the last field. There are probably other ways to solve this, this is just the way that has been chosen (back in serde 0.2)


#3

That’s what happens when you type on a phone :stuck_out_tongue_closed_eyes:

Ah I see, so it’s definitely important to follow that pattern then if any Serializer could assume you serialise a single value at a time.

EDIT: Just checked and both snippets do work:

First:

extern crate serde;
extern crate serde_json;

struct Point {
    pub x: f32,
    pub y: f32
}

struct PointMapVisitor<'a> {
    value: &'a Point,
    state: u8,
}

impl<'a> serde::ser::MapVisitor for PointMapVisitor<'a> {
    fn visit<S>(&mut self, serializer: &mut S) -> Result<Option<()>, S::Error>
        where S: serde::Serializer
    {
        match self.state {
            0 => {
                self.state += 1;
                Ok(Some(try!(serializer.serialize_struct_elt("x", &self.value.x))))
            }
            1 => {
                self.state += 1;
                Ok(Some(try!(serializer.serialize_struct_elt("y", &self.value.y))))
            }
            _ => {
                Ok(None)
            }
        }
    }
}

impl serde::ser::Serialize for Point {
    fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error> where S: serde::ser::Serializer {
        serializer.serialize_struct("", PointMapVisitor { state: 0, value: self }).map(|_| ())
    }
}

fn main() {
    let point = Point { x: 1.0, y: 1.0 };
    let ser = serde_json::to_string(&point).unwrap();

    println!("{}", ser);
}

Second:

extern crate serde;
extern crate serde_json;

struct Point {
    pub x: f32,
    pub y: f32
}

struct PointMapVisitor<'a> {
    value: &'a Point
}

impl<'a> serde::ser::MapVisitor for PointMapVisitor<'a> {
    fn visit<S>(&mut self, serializer: &mut S) -> Result<Option<()>, S::Error>
        where S: serde::Serializer
    {
        try!(serializer.serialize_struct_elt("x", &self.value.x));
        try!(serializer.serialize_struct_elt("y", &self.value.y));

        Ok(None)
    }
}

impl serde::ser::Serialize for Point {
    fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error> where S: serde::ser::Serializer {
        serializer.serialize_struct("", PointMapVisitor { value: self }).map(|_| ())
    }
}

fn main() {
    let point = Point { x: 1.0, y: 1.0 };
    let ser = serde_json::to_string(&point).unwrap();

    println!("{}", ser);
}

#4

do they produce the same json string? Serde doesn’t statically verify correct usage of its API (yet)


#5

They do actually, I’ll have to dig into the json serialiser to find out why… I would’ve thought each field would emit a comma, then the key and value unless the last non whitespace char was a brace or something to that effect.


#6

That would be quite the magical hackery. Instead serde-json simply has a first field in the map/struct serialization code, that makes sure the first field doesn’t get a comma prepended. It’s exactly that structure that requires the state machine.


#7

Hi,

I’m about to do some manual serialization as well (I want to get rid of syntex crate as it makes my build times slow :()

And I just wondered if you ran into some more issues with this approach? As it seems nicer than the code outlined in the examples.

Also did you manage to slim down the deserialize part as well?

Cheers


#8

It seems like the way first is set in the serializer is enough to get around putting commas in the right places without the state machine.

I have some fairly complex manual serialisation that’s never used the state machine approach and has always produced the correct output… Whether or not that makes it a good idea I’m not sure :smile:

I haven’t thought so much about the deserialiser though, I haven’t seen any alternative ways of deserialising values.


#9

Alright. Thanks for the info :slight_smile: