0

I have many value classes that make up a larger object case class.

final case class TopLevel(
  foo: Foo,
  bar: Bar
)

final case class Foo(foo: String) extends AnyVal

final case class Bar(bar: String) extends AnyVal

object Foo {
  implicit val format = Json.valueFormat[Foo]
}

object Bar {
  implicit val format = Json.valueFormat[Bar]
}

object TopLevel {
  implicit val TopLevelFormat: OFormat[TopLevel] = Json.format[TopLevel]
}

This works fine, but when the top level case class has many arguments the construction objects soon pile up. I tried to do this implicitly, e.g.

object Format {
  implicit def jsonFormatter[A](): Format[A] = Json.valueFormat[A]
}

But I get

scala.ScalaReflectionException: <none> is not a method
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66

1 Answers1

1

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.

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66