jonathanstowe / JSON-Class

A Role to allow Raku objects to be constructed and serialised from/to JSON.
Artistic License 2.0
6 stars 6 forks source link

Possible re-work of JSON::Class #11

Open vrurg opened 1 year ago

vrurg commented 1 year ago

There is a little but non-zero chance that I'd be working on an alternate implementation for JSON::Class because I need something more configurable and extensible. Unfortunately, JSON::Class -> JSON::Unmarshal/Marshal lacks necessary scalability. I'd like to get something akin to LibXML::Class and XML::Class.

The question is about naming. It would be great if the re-work follows the kind of standard naming approach and, yes, be named JSON::Class. With zef ecosystem this is not a big deal as the author name would be different. But I'd like to synchronize the matter with you and know your opinion on this.

Alternatively, I can simply re-implement this module and submit a PR.

Once again, it is all in limbo now. I have an immediate solution for my current problem in mind, even though it requires either to somehow make marshal return raw structure (hash, list, whatever), or de-deserialize the JSON it produces (slow!).

jonathanstowe commented 1 year ago

I think broadly I concur with your take, historically I made JSON::Class the way it is currently because JSON::Unmarshal already existed and that was half the problem solved and I could move on and make the things I wanted to make with it.

Having the functionality split across three or more modules also poses a logistical problem as well as a technical one, there are definitely features that I have postponed or only partially completed due to the need for symmetrical changes in JSON::Unmarshal/JSON::Marshal and so forth.

Also there are things in e.g. JSON::Unmarshal/JSON::Marshal which are not specific to the (de-)serialization of JSON but which functionality is hidden away in a way that makes re-use difficult, hence I wound up re-implementing parts of both for e.g. MessagePack::Class etc and is behind the issue that you describe.

So in general I'd be open to re-working JSON::Class such that it implements the Object to Structure round trip itself, but I'd suggest abstracting the non-JSON specific parts to a separate Role such that is can be re-used by e.g. MessagePack::Class, a possible YAML::Class or any other thing where the representation can map to some native data structure in Raku.

It might be worth starting with a PoC that basically refactors J::M/J::UM into a role with more public methods and see how that works ...

Xliff commented 1 year ago

And here I was about to submit a PR that allows one to define a json-unmarshal method an attribute can compose, that will allow one to do away with repeated is unmarshalled-by traits for the same type. I have an immediate need, so I'll probably be submitting it anyways.

jonathanstowe commented 1 year ago

Do it anyway, as who knows when this will happen. My personal preference is not specially named methods, but rather some trait that indicates the purpose of the method (so something like is unmarshal-for(Type) or such...)

Xliff commented 1 year ago

@jonathanstowe - Well, then you might not like my quick and dirty solution, which allowed me to write a single role to handle a dozen attributes without having to write the same is unmarshaled-by statement for each. What I did was use a custom trait is json-date-time, which applies a role to the associated attribute, which then adds the .json-unmarshal method that is called by JSON::Unmarshal if it exists.

I'm all for the is unmarshal-for(Type) mechanism, if you can tell me how it allows for reuse.

Thanks

Xliff commented 1 year ago

After thinking about it a bit longer, I do see where you are going with the is unmarshal-for(Type) option, however I don't think it provides enough distinction for things like:

Those are my initial thoughts. Please let me know if you have any suggestions.

jonathanstowe commented 1 year ago

I'd see it as doing something like:

has DateTime $.my-date;

method unmarshal-datetime(Str $d --> DateTime ) is unmarshal-for(DateTime) {
....
}

Then the unmarshaller can inspect the methods to find those which do some role that that the trait has added and match the type supplied to the type of the attribute being unmarshalled. Arguably the specific type could be omitted and it could just match the return type of the method. It could even be a multi with different subsets or where clauses to handle different cases of the input format.

A method defined like that can be re-used by putting it in a role and consuming that.

jonathanstowe commented 1 year ago
  • Proper handling between different formats (one use case for me is using both XML::Class and JSON::Class on the same object and they have requirements that differ by format)

The methods can have multiple candidates with arguments constrained by subset for example.

  • Might prove difficult to use if requirements for Type differ across classes in the same project.

I'm pretty sure any scheme would have this feature if implemented in a monolithic fashion. There is no reason to stop you from having separate roles for different requirements, or just a refinement of the multi candidates.

Xliff commented 1 year ago
  • Proper handling between different formats (one use case for me is using both XML::Class and JSON::Class on the same object and they have requirements that differ by format)

Given the example here:

has DateTime $.my-date;

method unmarshal-datetime(Str $d --> DateTime ) is unmarshal-for(DateTime) {
....
}

How would unmarshal-datetime know how to handle the difference between XML data and JSON data?

The methods can have multiple candidates with arguments constrained by subset for example.

I'm also having a little trouble seeing how subsets would help. Maybe a bit more elaboration into what you are describing?

vrurg commented 11 months ago

After some consideration, I decided to give the ecosystem a stress-test and went on with my own JSON::Class implementation. Not publishing it yet because there are not docs yet; but, first of all, I'd like get in sync here. For example, modules depending on the current JSON::Class are better be given :auth<zef:jonathanstowe> in their dependencies first.