dtolnay/typetag

`ron`'s output is very odd when combined with `typetag`

LikeLakers2 opened this issue · 6 comments

(most of this is copied from ron-rs/ron#452, with some edits, so apologies if some information here seems odd)

Hi! I was experimenting with typetag, and I found that there's some oddities with what ron outputs when using it.

Here's the code I'm using:

/Cargo.toml
[package]
name = "serde_ron_tag_test"
version = "0.1.0"
edition = "2021"

[dependencies]
	[dependencies.ron]
	version = "0.8"
	
	[dependencies.serde]
	version = "1"
	features = ["derive"]
	
	[dependencies.typetag]
	version = "0.2"
/src/main.rs
use serde::{Deserialize, Serialize};

#[typetag::serde]
trait Event {
	fn inspect(&self);
}

#[derive(Serialize, Deserialize)]
struct Message {
	data: String,
}

#[typetag::serde]
impl Event for Message {
	fn inspect(&self) {
		println!("type A with data `{}`", self.data);
	}
}

fn main() {
	let req_ser = ron::ser::to_string_pretty(
		&Message { data: "abcd".to_string() } as &dyn Event,
		ron::ser::PrettyConfig::default()
	).unwrap();
	
	println!("{}", req_ser);
	
	let req_de: Box<dyn Event> = ron::de::from_str(req_ser.as_str()).unwrap();
	
	req_de.inspect();
}

I would expect it to output something along the lines of this:

Message(
	data: "abcd",
)

since typetag is described as effectively creating an enum containing all types that implement that trait, and this is how ron normally outputs enums.

However, it instead outputs this mess:

{
	"Message": (
		data: "abcd",
	),
}

which looks very ugly and hard to understand when saved to a file. Unfortunately, I haven't found a way to make it output in the format I expect.

According to @juntyr when I posted about this over at ron-rs/ron:

This unfortunately seems to be an issue in typetag, see [1]

From briefly scanning the typetag codebase, the “enums” are not actually serialised as enums but as maps using the encoding that JSON uses for enums. Therefore, RON sees only that, in your case, a map with a single key is serialised. Unfortunately, this appears to be an issue with serde and it’s adjacent crates often assuming that every format is like JSON and can only treat enums as maps.

though I would suspect that anyone working on typetag may understand the issue in far greater detail.

In any case, I was hoping this could be looked into?


P.S. The problem also exists for enums:

/src/main.rs
use serde::{Deserialize, Serialize};

#[typetag::serde]
trait Event {
	fn inspect(&self);
}

#[derive(Serialize, Deserialize)]
enum Message {
	A { data: String },
	B { data: String },
}

#[typetag::serde]
impl Event for Message {
	fn inspect(&self) {
		match self {
			Self::A { data } => println!("type A with data `{data}`"),
			Self::B { data } => println!("type B with data `{data}`"),
		}
	}
}

fn main() {
	let req_ser = ron::ser::to_string_pretty(
		&Message::A { data: "abcd".to_string() } as &dyn Event,
		ron::ser::PrettyConfig::default()
	).unwrap();
	
	println!("{}", req_ser);
	
	let req_de: Box<dyn Event> = ron::de::from_str(req_ser.as_str()).unwrap();
	
	req_de.inspect();
}
Expected Output
A(
	data: "abcd",
)
Actual Output
{
	"Message": A(
		data: "abcd",
	),
}

As far as I can tell, this is behaving correctly. Serde data model's externally tagged enums are identified by a discriminant (index), for example which is a thing that most binary formats would need for serializing an enum. So that is not an appropriate representation for typetag trait impls which are not ordered. You'd need to pursue fixing this in ron to produce the output you prefer.

If I may ask... Looking through the documentation for the Serializer trait, I found Serializer::is_human_readable. Is it possible to serialize to an enum when dealing with a human-readable format, and to a map when dealing with a binary format?

It would be possible, but that would imply generating twice as much code for every type, which is most likely not the right tradeoff. See #25.

In any case, could this issue be re-opened for the time being, until a solution is found on either end? Even if the issue is on rons end, knowing what other formats it impacts (if any) would go a long way towards finding a solution, which means it would be useful to keep it open until then.

juntyr commented

As far as I can tell, this is behaving correctly. Serde data model's externally tagged enums are identified by a discriminant (index), for example which is a thing that most binary formats would need for serializing an enum. So that is not an appropriate representation for typetag trait impls which are not ordered. You'd need to pursue fixing this in ron to produce the output you prefer.

I’m not sure I understand the reason for serialising tagged enums as maps. While some data formats such as JSON use maps for this purpose, other formats like RON have specific syntax for them and could handle them better if serialize_newtype_variant (for externally tagged enums) and serialize_struct_variant (for adjacently tagged and internally tagged since that just seems to use a default content key) were used. Could this perhaps be changed or an option added to generate these better-typed imps instead?

@dtolnay I apologize for bumping this, but I'd like to request again that the issue at least be re-opened for discussion, even if it'll be some time before you respond to the above questions.