When writing JSON encoder/decoders I often find myself not wanting to annotate them as implicit
, as there are often multiple possible implementation (each for a different scenario). I want the user to decide which one to use explicitly at the use site.
An example of this is JSON parsing for java.time.LocalTime. In some endpoints, having the second part (“12:00:15”) makes sense, while for other endpoints it doesn’t (“12:00”).
Defining my decoder implementations (note the lack of implicits):
object JsonEncoders {
val localTimeWithSeconds: Encoder[LocalTime] = ...
val localTimeNoSeconds: Encoder[LocalTime] = ...
}
In order to bring them into scope as an implicit, I have to name them since I need to use implicit val
implicit val encodeWithSeconds = JsonEncoders.localTimeWithSeconds
useTheImplicit(...)
This gets worse if I need to bring in multiple implicits into scope
I propose allowing the usage of implicit
with import
to, which yields the the following:
import implicit JsonEncoders.localTimeWithSeconds
useTheImplicits(...)
I think this is much cleaner, and importing multiple things as implicits are also much cleaner
import implicit JsonEncoders.{localTimeWithSeconds, instantDateOnly}
useTheImplicits(...)
Things to Discuss
“import implicit” or “implicit import”
I think either way works. Former may be less confusing for newcomers(?). The latter fits the pattern of ‘implicit val’, ‘implicit def’
Ban blanket imports
What will the behaviour be for import implicit JsonEncoders._
?
In this case it’ll import def
s and val
s in the given scope as implicits.
I think we should ban this, as it is extremely dangerous. (Adding a val
, def
will have unforseen impact on any code that blanket-imported the current object/class)
Other remarks
Personally I try to avoid defining implicits
if I can, and prefer using locally-scoped implicits
to make it clear what implicits are active in the current scope.
I believe this really helps prevent the wrong implicits from being used accidentally (e.g. ExecutionContext, Encoders, etc)
Having some syntatic sugar will help a lot.
TL;DR:
def sendToA() = {
import implicit JsonEncoders.{encodeInstantWithSeconds, encodeLocalTimeWithSeconds}
}
def sendToA() = {
implicit val instantEncode = JsonEncoders.encodeInstantWithSeconds
implicit val localtimeEncode = JsonEncoders.encodeInstantWithSeconds
}