greglook / clj-cbor

Native Clojure CBOR codec implementation.
The Unlicense
70 stars 7 forks source link

Support inheritance-based fallback for write-handlers #12

Closed greglook closed 4 years ago

greglook commented 5 years ago

There are a few real-world use-cases that have cropped up where the majority of a type's API uses an abstract parent class or interface, with multiple underlying concrete implementations. One example is Joda's org.joda.time.DateTimeZone and the attendant CachedDateTimeZone, FixedDateTimeZone, and DateTimeZoneBuilder$PrecalculatedZone (yuck). The user wants to be able to treat all of these types uniformly, so having to declare every possible subclass makes type extension awkward here.

This can be solved by attempting inheritance-based resolution, similar to puget.dispatch/inheritance-lookup.

usernolan commented 5 years ago

I'm running into this scenario, and think this feature would be great. Another place I've seen something similar is in transit. If I have time I will look further into how something like this would fit into clj-cbor and put together a PR.

In any case, thanks for the work you've put into this lib, it's a breeze to work with.

greglook commented 5 years ago

Thanks! Always great to hear positive feedback.

I looked into this a bit more, and there are ways to add this functionality with the existing code. In write-handled, the codec uses the :dispatch function to generate a lookup value, then calls :write-handlers as a function on that lookup to find if there is a custom handler:

https://github.com/greglook/clj-cbor/blob/34ffb6f79d136ffaa56cc99e7ab5a112d02ce11d/src/clj_cbor/codec.clj#L690-L692

That means that a caller could either use a custom dispatch function which maps known problem classes to the desired lookup value, or they could replace the default hash-map for write-handlers with something that does more sophisticated lookup behavior.

usernolan commented 5 years ago

That looks great—it's cool that the library supports this without further modification.

Just to make sure I'm interpreting this correctly, are you thinking that the custom dispatch function would be convenient for manually specifying how a class should be mapped to a handler, while replacing write-handlers with something more sophisticated would capture more generic changes to the lookup algorithm?

I think I'm getting it, and if so, that solves the whole problem really cleanly. Thanks for pointing this out, this was mega helpful.

greglook commented 5 years ago

Yeah - the dispatch function would let you alias specific types if you wanted to. To use the DateTimeZone example, you could do this to address it:

(defn encoder-class
  [x]
  (if (instance? DateTimeZone x)
    DateTimeZone
    (class x)))

(def codec
  (cbor/cbor-codec :dispatch encoder-class))

You could also address it more generally by replacing the write handlers, similar to how Puget does it.

greglook commented 4 years ago

Added a utility function for this and released it in 1.0.0.