aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/DerivedFormats.scala
blob: 6c2396ef7c8ecf35fbd17629403f86174b4f0052 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import magnolia._
import spray.json._

import scala.language.experimental.macros

trait JsonFormatDerivation extends DefaultJsonProtocol {
  type Typeclass[T] = JsonFormat[T]

  def combine[T](ctx: CaseClass[JsonFormat, T]): JsonFormat[T] = new JsonFormat[T] {
    override def write(value: T): JsValue = {
      val fields: Seq[(String, JsValue)] = ctx.parameters.map { param =>
        param.label -> param.typeclass.write(param.dereference(value))
      }
      JsObject(fields: _*)
    }
    override def read(value: JsValue): T = value match {
      case obj: JsObject =>
        ctx.construct { param =>
          param.typeclass.read(obj.fields(param.label))
        }
      case js =>
        deserializationError(s"Cannot read JSON '$js' as a ${ctx.typeName}")
    }
  }

  def dispatch[T](ctx: SealedTrait[JsonFormat, T]): JsonFormat[T] = new JsonFormat[T] {
    override def write(value: T): JsValue = {
      ctx.dispatch(value) { sub =>
        val obj = sub.typeclass.write(sub.cast(value)).asJsObject
        JsObject((obj.fields ++ Map("type" -> JsString(sub.label))).toSeq: _*)
      }
    }
    override def read(value: JsValue): T = value match {
      case obj: JsObject if obj.fields.contains("type") =>
        val fieldName = obj.fields("type").convertTo[String]

        ctx.subtypes.find(_.label == fieldName) match {
          case Some(tpe) => tpe.typeclass.read(obj)
          case None =>
            deserializationError(
              s"Cannot deserialize JSON to ${ctx.typeName} because type field '${fieldName}' has an unsupported value.")
        }

      case js =>
        deserializationError(s"Cannot read JSON '$js' as a ${ctx.typeName}")
    }

  }

  implicit def gen[T]: JsonFormat[T] = macro Magnolia.gen[T]

}
object JsonFormatDerivation extends JsonFormatDerivation

trait DerivedFormats extends JsonFormatDerivation