format
and valueFormat
are macros (well, actually not themselves but they call macros). So you can't call them where you want. Json.valueFormat[A]
is trying to expand where A
is not a value class yet but an abstract type (a type parameter). If you'd like to postpone expansion of the macros you can make your implicits implicit macros
// in a different subproject
import play.api.libs.json.{Format, OFormat}
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
object Format {
implicit def valueFormat[A]: Format[A] = macro MacroImpl.valueFormatImpl[A]
implicit def format[A]: OFormat[A] = macro MacroImpl.formatImpl[A]
}
class MacroImpl(val c: whitebox.Context) {
import c.universe._
val json = q"_root_.play.api.libs.json.Json"
def valueFormatImpl[A: WeakTypeTag]: Tree = q"$json.valueFormat[${weakTypeOf[A]}]"
def formatImpl[A: WeakTypeTag]: Tree = q"$json.format[${weakTypeOf[A]}]"
}
Now you don't need to define implicits in companion objects of your classes or value classes. You just need to import implicits from Format
to where you need them
final case class TopLevel(foo: Foo, bar: Bar)
final case class Foo(foo: String) extends AnyVal
final case class Bar(bar: String) extends AnyVal
import Format._
implicitly[Format[Foo]].writes(Foo("a")) // {"foo":"a"}
implicitly[Format[Bar]].writes(Bar("b")) // {"bar":"b"}
implicitly[Format[TopLevel]].writes(TopLevel(Foo("a"), Bar("b"))) // {"foo":{"foo":"a"},"bar":{"bar":"b"}}
implicitly[OFormat[Foo]].writes(Foo("a")) // {"foo":"a"}
implicitly[OFormat[Bar]].writes(Bar("b")) // {"bar":"b"}
implicitly[OFormat[TopLevel]].writes(TopLevel(Foo("a"), Bar("b"))) // {"foo":{"foo":"a"},"bar":{"bar":"b"}}
Alternatively, you can define macro annotation for classes or companion objects. It will generate necessary implicits inside companion objects. But I guess now implicit macros are a little easier than macro annotation.