diff options
Diffstat (limited to 'src/main/scala/DerivedFormats.scala')
-rw-r--r-- | src/main/scala/DerivedFormats.scala | 119 |
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 |