diff --git a/play-json-generic/src/main/scala-3/com/evolution/playjson/generic/FlatTypeReads.scala b/play-json-generic/src/main/scala-3/com/evolution/playjson/generic/FlatTypeReads.scala index a5b1910..f918c87 100644 --- a/play-json-generic/src/main/scala-3/com/evolution/playjson/generic/FlatTypeReads.scala +++ b/play-json-generic/src/main/scala-3/com/evolution/playjson/generic/FlatTypeReads.scala @@ -10,6 +10,9 @@ import scala.annotation.nowarn * It will look for a `type` field in the JSON object and use its value to determine which subtype to use. * The `type` field will be removed from the JSON object before the subtype's `Reads` is called. * + * The difference between this class and `NestedTypeReads` is that this class uses the simple name of the subtype + * instead of the full prefixed name. This means that you cannot use the same simple name for multiple subtypes. + * * Example: * * {{{ diff --git a/play-json-generic/src/main/scala-3/com/evolution/playjson/generic/NestedTypeReads.scala b/play-json-generic/src/main/scala-3/com/evolution/playjson/generic/NestedTypeReads.scala index 2f9bcf5..9906c2b 100644 --- a/play-json-generic/src/main/scala-3/com/evolution/playjson/generic/NestedTypeReads.scala +++ b/play-json-generic/src/main/scala-3/com/evolution/playjson/generic/NestedTypeReads.scala @@ -4,6 +4,33 @@ import play.api.libs.json.* import scala.deriving.Mirror import scala.compiletime.* +/** + * This is a helper class for creating a `Reads` instance for a sealed trait hierarchy. + * It will look for a `type` field in the JSON object and use its value to determine which subtype to use. + * The `type` field will be removed from the JSON object before the subtype's `Reads` is called. + * + * The difference between this class and `FlatTypeReads` is that this class uses the full prefixed name of the subtype + * instead of just the simple name. This allows you to use the same simple name for multiple subtypes as long as they + * are located in different branches of the sealed trait hierarchy. + * + * Example: + * {{{ + * sealed trait Parent + * sealed trait Branch1 extends Parent + * sealed trait Branch2 extends Parent + * case class Child1(field1: String) extends Parent + * case class Child2(field2: Int) extends Parent + * + * object Child1: + * given Reads[Child1] = Json.reads[Child1] + * object Child2: + * given Reads[Child2] = Json.reads[Child2] + * + * val reads: NestedTypeReads[Parent] = summon[NestedTypeReads[Parent]] + * + * }}} + * + */ trait NestedTypeReads[T] extends Reads[T]: override def reads(jsValue: JsValue): JsResult[T] @@ -13,6 +40,14 @@ object NestedTypeReads: def create[A](f: JsValue => JsResult[A]): NestedTypeReads[A] = (jsValue: JsValue) => f(jsValue) + /** + * This is the first method that will be called when the compiler is looking for an instance of `NestedTypeReads`. + * It will look for a `type` field in the JSON object and use its value to determine which subtype of `A` to use. + * Then, it will look for an instance of `Reads` for that subtype and use it to read the JSON object. + * + * @param m + * @return + */ inline given derive[A](using m: Mirror.SumOf[A]): NestedTypeReads[A] = create[A] { jsValue => for { @@ -24,6 +59,16 @@ object NestedTypeReads: } yield result } + /** + * Traverse the tuple of types and look for a `Reads` instance for the type that matches the given `typ`. + * If no `Reads` instance is found, return `None`. Otherwise, return the `Reads` instance. + * This method is used to traverse the tuple of types that represent the subtypes of a sealed trait. + * + * @param typ the value of the `type` field in the JSON object. + * @param prefix the prefix to use when building the full prefixed name of the subtype. + * For example, if the `prefix` is `com.example` and the `typ` is `Child1`, the full prefixed name of the subtype + * is `com.example.Child1`. If the `prefix` is empty, the full prefixed name of the subtype is just the `typ`. + */ private inline def deriveNestedTypeReadsForSum[A, T <: Tuple]( typ: String, prefix: String