circe-optics
circe-optics copied to clipboard
No way to create a Fold to a specific A type without Encoder[A]
Problem
Consider a JSON:
{"foo": {"bar": {"goo": {"car": { /* some complex structure inside */ }}}}}
We need to parse and decode the car
object into some model:
class Car( /* some accordingly complex structure inside */ )
We don't care about writing this model back to JSON so we only have implemented a Decoder
for it:
implicit val carDecoder: Decoder[Car] = { cursor => ??? /* some custom decoder */ }
Now we're attempting to create a JSON path based optic to access it:
JsonPath.root.foo.bar.goo.car.as[Car] // does not compile: could not find implicit value for parameter encode: io.circe.Encoder[Car]
This line fails to compile since the as
method requires both Decoder
and Encoder
(which makes it different from Json#as
for example).
Having an Encoder
instance could come in handy if we wanted to set/modify the source JSON with some new or update Car
instance. But if we don't need to do it (I bet it is 90% or even 99% cases of what JSON paths are created for), then the Encoder
instance is not used somehow and just does not allow us to read our model in this way if the model does not have an encoder.
The same can be applied to JsonTraversalPath
and JsonFoldPath
.
But the latter case is a bit ridiculous, because JsonFoldPath
creates a Fold
instance, but still requires an Encoder
.
The Fold
optic cannot be used for "set" or "modify" operations so that encoder
comes completely redundant.
One of the ways this could be alleviated is to create explicitly named as*
methods which would be free of the Encoder
requirements. For example, JsonTraversalPath
and JsonFoldPath
could get:
def asFold[A: Decoder]: Fold[Json, A] = ???
JsonPath
could also get asFold
but basically it models 1 or 0 targets optic, therefore I think the best bet for it would be
def asGetter[A: Decoder]: Getter[Json, Option[A]] = ???