Possible to serializer/deserialize and object like Ruby marshaling?

I been thinking about this a bit as I came up with a better way to implement CrSerializer that would be more flexible. However I think it’s important for us to define what serialization in Crystal should look like, and the goals we wish to achieve. Then from there we have some criteria to evaluate various implementations.

Some of my thoughts/ideas.

Annotations

Annotations should be used to control how properties get serialized/deserialized in a (mostly) format agnostic way. The current usage of using JSON::Field and YAML::Field is not scalable if every format needs its own annotation.

However, if each format has its own; it would allow you to control how type is serialized on a per format basis. I kinda doubt this is common/useful enough to worry about?

Pluggable

Supporting a new format shouldn’t require anything more than defining how each type gets serialized. The “framework” around the serialization logic should be kept separate from the actual implementation.

There will be exceptions to this, for example XML: annotating a property with @[XmlAttribute] would be specific to how that property is serialized in XML

Flexible

The current implementations make it hard to add new features/customization beyond converters. The ideal framework would allow greater control over how, when, and if a property should be serialized.

Having some extra control/flexibility would be great. A few examples would be:

  • Serialize based on a version since/until x
  • Changing the view based on groups a property is in
  • Able to consume a property on deserialization but skip it on serialization
  • Something custom the user wants to implement
    • E.x. If a property should be serialized

API

I’m thinking a good api would be like

Serializer.serialize(data : _, format : Format, context : Context? = nil) : R

Where:

  • data - The obj/type you want to serialize
  • format - An Enum value representing the supported types
  • context - A class that could be used to pass generic data to the framework
    • Like which groups to use, or what version, etc
  • R - The return type, String, Bytes etc.

Then, we probably could retrain the to_json method but have it internally call serialize while passing an optional context object.

Final Thoughts

While this by no means represents how the actual implementation will be like, I think its a conversation we should start having sooner than later.

I think its also important to understand we don’t have to do all of this in macro land. Using macros/annotations to provide “metadata” objects for each property that can then be processed into the final output at runtime is much easier and gives way more flexibility.