Serde is one of Rust's killer feature which makes you appreciate the philosophy behind the language. With just a few annotations you have a robust and complete parser for whatever format (JSON, TOML, etc.) automatically built for serializing and deserializing your data structures. And it's fast, and optionnally zero-copy. I've never seen this in other languages.
Have you got a link to the Common Lisp library that lets you annotate data structures so you can use the annotations to parse or dump data by 3rd party libraries for various formats?
If you are asking if it's fast? yes it's fast serialization. Not sure which numbers you have at hand, but for me fast means it isn't the bottleneck of my application.
Haskell has been around for more than a decade and had huge uptake by language nerds, but the only significant OSS written in it is Pandoc. In contrast, Rust already has Servo and a number of CLI tools, ripgrep being the most prominent.
I'm not sure what you consider 'significant', but to my mind, there are many great haskell open source projects: xmonad, shellcheck, stack, haxl, to name a few.
Not to mention how many languages are written in it: Elm, Purescript, Idris, GHC itself.
True. Not widely available though. The great thing about Rust is that has a whole of the advantages of Haskell, and C++, and to a lesser extent even languages like Python.
It is best not to confuse deserialisation and parsing. Parsing is a separate task with separate issues.
For JSON deserialisation, Serde is a very convenient API to use an existing JSON parser that someone else has (hand) written.
If you needed a new syntax, you'd have to write a new parser. I often implement communications protocols as part of my work. In most cases, Serde doesn't help me much there - I have to parse the protocol myself (e.g. using Nom).
What is the difference between parsing and deserializing? With both you have a string representation of a data structure that you read and bring into the native form. Do you call it parsing if the grammar is more complicated?
Deserialisation (in this context) is the act of decoding data from some "string representation" to a representation suitable for use, typically a native format.
Typically, although this depends on the specific encoding, a parser is required as one of the first stages of a deserialiser; normally a parser refers to something which applies the rules of something as least as complex as a CFG, as opposed to a lexer/scanner which typically just applies regular expressions; a combination of the two allows the serialised format to be interpreted.
You can show quite easily that JSON (and indeed any CFL) cannot be "parsed" using a lexer alone.
I had the vague notion that parsing was for human readable content and deserialization was for computer-to-computer content.
Now I tentatively make the distinction that compiling transforms streams into code (stream -> lex -> parse -> magic -> code) whereas deserialization is for data. Making parsing just one of the transformation steps for both compilation and deserialization.
Serde includes a bunch of parsers by default but if you invented a new text file format, you’d have to write a new parser for it before you could do anything with it, and serde doesn’t help with that.
The really essential difference is that when you deserialize, you trust the input, and so don't need to check syntax or validity. I.e., you assume it is output of your own serializer, and you can therefore be faster than if you didn't trust it.
I have no idea if serde follows this design, or if it has just hijacked the terms.
Thanks for sharing. I'm not familiar with D, but it looks like compile time type introspection is something which is built into the language, which removes the need for something like the serde derive macro. Is that correct?
Yes. D metaprogramming is mostly based around template and it gives you compile-time traits which you can use to query structs/classes for their fields, determining if something is an array or a pointer etc.
This way, you can write a serialize(foo) function which will work for any type, without needing any derives or marking the type as Serializable like in Java.
The closest equivalent to derive in D would be a mixin. You could declare a mixin which autogenerates the toJSON() fromJSON() methods based on the fields of a struct.
Kotlin serialization[0] is similar to this, implemented via a compiler plugin that allows optimal serializers/deserializers to be generated at compile time.
Serde is very strong indeed (although not the easiest to understand, this would have been useful when I implemented a Serde library for [de]serialising with the AWS DynamoDB AttributeValue format).
That said, I've always had a slight problem with this approach to serialisation in general, which is that it makes it very hard to have multiple serialisation approaches for a single type (e.g. I want one casing rule for one target, and a different one for another). This seems effectively impossible with an approach based on annotations/macros. Would love to know how others have dealt with that.
> I want one casing rule for one target, and a different one for another
Hmm, this is interesting, and something I hadn't thought of before. I'd probably try to solve it with a combination of the newtype pattern (create wrapper types for `MyThing`, like `MyThingJson` and `MyThingToml`) and the Serde flatten attribute. Then you could, for example, add `#[serde(rename_all = "lowercase")]` to the `MyThingJson` struct and `#[serde(rename_all = "snake_case")]` to the `MyThingToml` struct.
I haven't tested this though, so I'm not sure if the `rename_all` would flow through the flatten attribute as expected. Worst case you could duplicate the struct (or more likely, find or build a macro to do it for you) rather than use the newtype/flatten technique.
This is where I would either a) create 2 separate DTO representations with adapters to the internal domain object or b) have a trait which the 2 specialized structs implement. This conversion point is also a good place to do your validation that the object is in a consistent state. This isn't quite zero-cost - but it does isolate your external serialization code from your internal logic and it is easier to add even more formats in the future.
And at least in Java-land is much cleaner than stacking lots and lots of annotations on the same struct.
Pronunciation question: Does Serde have one syllable or two? Looking at the word out of context, it seems the e at the end ought to be silent. But it's derived as an abbreviation from SERialize/DEserialize. Does the author or community have a consensus on one of these being the "correct" form?
Original author of serde here. I call it sir-dee, but the current maintainer, dtolnay, calls it sear-day. But we really don't care how people pronounce it. It's really more of a neither / potato thing:
To me, pronouncing it seer-dee would make sense, because that's how I'd pronounce seer-ialize / dee-serialize. However, for some reason I still pronounce it sir-dee regardless :)
39 comments
[ 3.2 ms ] story [ 36.8 ms ] threadOr now that I think of it, Template Haskell.
Not to mention how many languages are written in it: Elm, Purescript, Idris, GHC itself.
i guess today it only matter how many stars you repo has in github...
For JSON deserialisation, Serde is a very convenient API to use an existing JSON parser that someone else has (hand) written.
If you needed a new syntax, you'd have to write a new parser. I often implement communications protocols as part of my work. In most cases, Serde doesn't help me much there - I have to parse the protocol myself (e.g. using Nom).
Typically, although this depends on the specific encoding, a parser is required as one of the first stages of a deserialiser; normally a parser refers to something which applies the rules of something as least as complex as a CFG, as opposed to a lexer/scanner which typically just applies regular expressions; a combination of the two allows the serialised format to be interpreted.
You can show quite easily that JSON (and indeed any CFL) cannot be "parsed" using a lexer alone.
I had the vague notion that parsing was for human readable content and deserialization was for computer-to-computer content.
Now I tentatively make the distinction that compiling transforms streams into code (stream -> lex -> parse -> magic -> code) whereas deserialization is for data. Making parsing just one of the transformation steps for both compilation and deserialization.
I have no idea if serde follows this design, or if it has just hijacked the terms.
This way, you can write a serialize(foo) function which will work for any type, without needing any derives or marking the type as Serializable like in Java.
The closest equivalent to derive in D would be a mixin. You could declare a mixin which autogenerates the toJSON() fromJSON() methods based on the fields of a struct.
[0] https://github.com/Kotlin/kotlinx.serialization
https://raphlinus.github.io/rust/2019/08/21/rust-bloat.html#...
That said, I've always had a slight problem with this approach to serialisation in general, which is that it makes it very hard to have multiple serialisation approaches for a single type (e.g. I want one casing rule for one target, and a different one for another). This seems effectively impossible with an approach based on annotations/macros. Would love to know how others have dealt with that.
Hmm, this is interesting, and something I hadn't thought of before. I'd probably try to solve it with a combination of the newtype pattern (create wrapper types for `MyThing`, like `MyThingJson` and `MyThingToml`) and the Serde flatten attribute. Then you could, for example, add `#[serde(rename_all = "lowercase")]` to the `MyThingJson` struct and `#[serde(rename_all = "snake_case")]` to the `MyThingToml` struct.
I haven't tested this though, so I'm not sure if the `rename_all` would flow through the flatten attribute as expected. Worst case you could duplicate the struct (or more likely, find or build a macro to do it for you) rather than use the newtype/flatten technique.
And at least in Java-land is much cleaner than stacking lots and lots of annotations on the same struct.
This was where I first saw it as well. Years ago...
... Sorry, just trying to be funny.
https://www.youtube.com/watch?v=K75g7eRhH9M