Introduction
I had an opportunity to work with Symfony’s Serializer component recently. While I don’t think we need something like it in the stdlib, one part did stick out as possibly useful: how it uses an associative array as an intermediary between the raw format and an object.
Crystal currently handles this with a per-format data structure, JSON::Any
and YAML::Any
. These structures are very closely related in regards to their function and API:
Method | JSON::Any | YAML::Any | Notes |
---|---|---|---|
#==(other : self) | X | X | |
#==(other) | X | X | |
# | X | X | JSON has separate overloads which I guess makes it impossible to has Int32 keyed hashes? |
#? | X | X | Ditto |
#as_a, #as_a? | X | X | |
#as_bool, #as_bool? | X | X | |
#as_bytes, #as_bytes? | X | ||
#as_f, #as_f? | X | X | |
#as_f32, #as_f32? | X | X | |
#as_h, #as_h? | X | X | |
#as_i, #as_i? | X | X | |
#as_i64, #as_i64? | X | X | |
#as_nil | X | X | |
#as_s, #as_s? | X | X | |
#as_time, #as_time? | X | ||
#dig | X | X | |
#raw | X | X | |
#size | X | X |
Proposal
I had a thought of merging these two concepts into a Serializable::Any
. I also considered just Any
as there’s nothing that makes this specific to serialization, but I wanted to keep it more clear this is something used related to serialization, not just something you should reach for any time you want “dynamic” types. This type would then represent data currently in-between a serialization format, or a deserialized value. This comes with a few benefits that I can think of:
- Provide a common type to reduce duplication and enable standardization.
- Adding support for a new underlying type/value would work across the board
- Function as intermediary state for objects as well. i.e. https://github.com/crystal-lang/crystal/issues/6309
- Could be used to implement a single representation of the state of an obj to be (de)serialized, using standardized annotations as well
- Make it easier for third-party libs to handle custom formats. As if you create a shard to handle say
TOML
it would function and work with anything setup to handleSerializable::Any
, which includes both to/from-object and to/from-format directions.
Open Questions
- This is probably not a replacement to the current streaming APIs
- Given this approach needs to load all the data into memory, it wouldn’t be as efficient; but would be more flexible. Third-party shards could implement their own serialization logic using it if they want, but stdlib would still use
JSON::PullParser
for example.
- Given this approach needs to load all the data into memory, it wouldn’t be as efficient; but would be more flexible. Third-party shards could implement their own serialization logic using it if they want, but stdlib would still use
- How to implement this in a backwards compatible way?
- Given the APIs are so similar, we might be able to get away with just aliasing both the existing any types to this new type. Constructors are basically the same, minus the one specific to
JSON::PullParser
and/orYAML::ParseContext
, so TBD how to handle that - Another option would be an entirely separate method/alternate to the existing
.parse
methods that returns this type instead. - Option three, provide some built-in way to pass an existing any type to this type and it’ll convert itself.
- Expanding upon this a bit more, if we make this new any type also implement https://github.com/crystal-lang/crystal/issues/10886
- Given the APIs are so similar, we might be able to get away with just aliasing both the existing any types to this new type. Constructors are basically the same, minus the one specific to
- How to handle forward compatibility given adding say
#to_i128
could be considered a breaking change? - TBD
Starting this off as a forum thread as I’d like to get some feedback/flesh things out a bit more before I think it’s actionable.