From 7ab0889d7ab4480d0d90346432006ffd345b4916 Mon Sep 17 00:00:00 2001 From: Jakob Odersky Date: Wed, 14 Feb 2018 15:00:52 -0800 Subject: Remove need for special case for enums --- README.md | 14 +++-- src/main/scala/DerivedFormats.scala | 86 ++++++++++++------------------- src/main/scala/annotations.scala | 9 ---- src/test/scala/CoproductTypeFormats.scala | 9 ++-- 4 files changed, 49 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index 9cc0cce..de5e4fd 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,17 @@ # Spray JSON Format Derivation -This library provides automatic spray JsonFormats for any `case class` and children of `sealed trait`s. - -It uses the [Magnolia](http://magnolia.work/) ([source code](https://github.com/propensive/magnolia)) type-derivation library to implicitly generate JSON formats for any product type. Magnolia integrates with spray so seamlessly that it is almost not worth the effort to publish this project as a full fledged repository; a single gist with the contents of [DerivedFormats.scala](src/main/scala/DerivedFormats.scala) would demonstrate almost all functionality. +This library provides automatic spray JsonFormats for any `case class` +and children of `sealed trait`s. + +It uses the [Magnolia](http://magnolia.work/) ([source +code](https://github.com/propensive/magnolia)) type-derivation library +to implicitly generate JSON formats for any product type. Magnolia +integrates with spray so seamlessly that it is almost not worth the +effort to publish this project as a full fledged repository; a single +gist with the contents of +[DerivedFormats.scala](src/main/scala/DerivedFormats.scala) would +demonstrate almost all functionality. ## Getting Started diff --git a/src/main/scala/DerivedFormats.scala b/src/main/scala/DerivedFormats.scala index 08c452c..4fc096e 100644 --- a/src/main/scala/DerivedFormats.scala +++ b/src/main/scala/DerivedFormats.scala @@ -27,7 +27,7 @@ trait DerivedFormats { self: BasicFormats => ctx.construct { param => param.typeclass.read(obj.fields(param.label)) } - case str: JsString if ctx.isObject && str.value == ctx.typeName.short => + case JsString(str) if ctx.isObject && str == ctx.typeName.short => ctx.rawConstruct(Seq.empty) case js => @@ -36,64 +36,46 @@ trait DerivedFormats { self: BasicFormats => } } - 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) - } + def dispatch[T](ctx: SealedTrait[JsonFormat, T]): JsonFormat[T] = { + val typeFieldName = ctx.annotations + .collectFirst{ + case g: gadt => g.typeFieldName + } + .getOrElse("type") - case g: gadt => - ctx.dispatch(value) { sub => - val obj = sub.typeclass.write(sub.cast(value)).asJsObject + new JsonFormat[T] { + override def write(value: T): JsValue = ctx.dispatch(value) { sub => + sub.typeclass.write(sub.cast(value)) match { + case obj: JsObject => JsObject( - (Map(g.typeFieldName -> JsString(sub.typeName.short)) ++ + (Map(typeFieldName -> JsString(sub.typeName.short)) ++ obj.fields).toSeq: _*) - } + case js => js + } } - 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.") - } + override def read(js: JsValue): T = { + val typeName: String = js match { + case obj: JsObject if obj.fields.contains(typeFieldName) => + obj.fields(typeFieldName).convertTo[String] + case JsString(str) => + str + case _ => + deserializationError( + s"Cannot deserialize JSON to ${ctx.typeName.full} " + + "because serialized type cannot be determined.") + } - case js => - deserializationError( - s"Cannot read JSON '$js' as a ${ctx.typeName}") - } + ctx.subtypes.find(_.typeName.short == typeName) match { + case Some(tpe) => tpe.typeclass.read(js) + case None => + deserializationError( + s"Cannot deserialize JSON to ${ctx.typeName.full} " + + s"because type '${typeName}' is unsupported.") + } } - } + } + } implicit def gen[T]: JsonFormat[T] = macro Magnolia.gen[T] diff --git a/src/main/scala/annotations.scala b/src/main/scala/annotations.scala index f23fbcb..ee179ff 100644 --- a/src/main/scala/annotations.scala +++ b/src/main/scala/annotations.scala @@ -2,9 +2,6 @@ package xyz.driver.json import scala.annotation.StaticAnnotation -/** Indicator trait of anontations related to JSON formatting. */ -sealed trait JsonAnnotation - /** An annotation that designates that a sealed trait is a generalized algebraic * datatype (GADT), and that a type field containing the serialized childrens' * types should be added to the final JSON objects. @@ -25,9 +22,3 @@ sealed trait JsonAnnotation * object */ final class gadt(val typeFieldName: String = "type") extends StaticAnnotation - with JsonAnnotation - -/** An annotation that designates that a sealed trait is an enumeration (all - * children are strictly case objects), and that all children should be - * serialized as strings. */ -final class enum extends StaticAnnotation with JsonAnnotation diff --git a/src/test/scala/CoproductTypeFormats.scala b/src/test/scala/CoproductTypeFormats.scala index f16e4c7..f9b71b8 100644 --- a/src/test/scala/CoproductTypeFormats.scala +++ b/src/test/scala/CoproductTypeFormats.scala @@ -31,10 +31,10 @@ class CoproductTypeFormats """{"type":"Plus","lhs":{"type":"Value","x":42},"rhs":{"type":"Value","x":0}}""" ) - // "Case object child" should behave like checkCoherence[Expr]( - // One, - // """{"type":"One"}""" - // ) + "Case object child" should behave like checkCoherence[Expr]( + One, + """"One"""" + ) @gadt("kind") sealed abstract class Keyword(`type`: String) @@ -45,7 +45,6 @@ class CoproductTypeFormats """{"kind":"If","type":"class"}""" ) - @enum sealed trait Enum case object A extends Enum case object B extends Enum -- cgit v1.2.3