aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/DerivedFormats.scala
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/scala/DerivedFormats.scala')
-rw-r--r--src/main/scala/DerivedFormats.scala119
1 files changed, 82 insertions, 37 deletions
diff --git a/src/main/scala/DerivedFormats.scala b/src/main/scala/DerivedFormats.scala
index 6c2396e..79c1e4d 100644
--- a/src/main/scala/DerivedFormats.scala
+++ b/src/main/scala/DerivedFormats.scala
@@ -1,55 +1,100 @@
+package xyz.driver.json
+
import magnolia._
import spray.json._
import scala.language.experimental.macros
-trait JsonFormatDerivation extends DefaultJsonProtocol {
+trait DerivedFormats { self: BasicFormats =>
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))
+ def combine[T](ctx: CaseClass[JsonFormat, T]): JsonFormat[T] =
+ new JsonFormat[T] {
+ override def write(value: T): JsValue =
+ if (ctx.isObject) {
+ JsString(ctx.typeName.short)
+ } else {
+ val fields: Seq[(String, JsValue)] = ctx.parameters.map { param =>
+ param.label -> param.typeclass.write(param.dereference(value))
+ }
+ JsObject(fields: _*)
}
- 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 =>
+ ctx.construct { param =>
+ param.typeclass.read(obj.fields(param.label))
+ }
+ case str: JsString if ctx.isObject && str.value == ctx.typeName.short =>
+ ctx.rawConstruct(Seq.empty)
+
+ case js =>
+ deserializationError(
+ s"Cannot read JSON '$js' as a ${ctx.typeName.full}")
}
}
- 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}")
- }
+ def dispatch[T](ctx: SealedTrait[JsonFormat, T]): JsonFormat[T] =
+ new JsonFormat[T] {
+ def tpe =
+ ctx.annotations
+ .find(_.isInstanceOf[JsonAnnotation])
+ .getOrElse(new gadt("type"))
+
+ override def write(value: T): JsValue = tpe match {
+ case _: enum =>
+ ctx.dispatch(value) { sub =>
+ JsString(sub.typeName.short)
+ }
- }
+ case g: gadt =>
+ ctx.dispatch(value) { sub =>
+ val obj = sub.typeclass.write(sub.cast(value)).asJsObject
+ JsObject(
+ (Map(g.typeFieldName -> JsString(sub.typeName.short)) ++
+ obj.fields).toSeq: _*)
+ }
+ }
+
+ override def read(value: JsValue): T = tpe match {
+ case _: enum =>
+ value match {
+ case str: JsString =>
+ ctx.subtypes
+ .find(_.typeName.short == str.value)
+ .getOrElse(deserializationError(
+ s"Cannot deserialize JSON to ${ctx.typeName.full} because " +
+ "type '${str}' has an unsupported value."))
+ .typeclass
+ .read(str)
+ case js =>
+ deserializationError(
+ s"Cannot read JSON '$js' as a ${ctx.typeName.full}")
+ }
+
+ case g: gadt =>
+ value match {
+ case obj: JsObject if obj.fields.contains(g.typeFieldName) =>
+ val fieldName = obj.fields(g.typeFieldName).convertTo[String]
+
+ ctx.subtypes.find(_.typeName.short == fieldName) match {
+ case Some(tpe) => tpe.typeclass.read(obj)
+ case None =>
+ deserializationError(
+ s"Cannot deserialize JSON to ${ctx.typeName.full} " +
+ s"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
+object DerivedFormats extends DerivedFormats with BasicFormats