diff --git a/README.md b/README.md index beecbbc..43b4275 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,8 @@ If the case class contains 2 fields or more, Play's [JSON macro inception](http: This is often more convenient than Play's default format ```{"name": "San Francisco"}```. +If you would rather stick to Play's default format even for single field case classes, you can use ```@jsonstrict``` instead of ```@json```. + #Installation If you're using Play (version 2.1 or higher) with SBT, you should add the following settings to your build: diff --git a/build.sbt b/build.sbt index 37c4d23..723a46f 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ organization := "com.kifi" name := "json-annotation" -version := "0.1" +version := "0.2" scalaVersion := "2.11.1" diff --git a/src/main/scala/com/kifi/macros/JsonFormatAnnotation.scala b/src/main/scala/com/kifi/macros/JsonFormatAnnotation.scala index 9e4f068..a2cf429 100644 --- a/src/main/scala/com/kifi/macros/JsonFormatAnnotation.scala +++ b/src/main/scala/com/kifi/macros/JsonFormatAnnotation.scala @@ -6,6 +6,9 @@ import scala.annotation.StaticAnnotation import CrossVersionDefs._ +object jsonMacroInstance extends jsonMacro(false) +object jsonStrictMacroInstance extends jsonMacro(true) + /** * "@json" macro annotation for case classes * @@ -21,10 +24,24 @@ import CrossVersionDefs._ * then A(4) will be serialized as '4' instead of '{"value": 4}'. */ class json extends StaticAnnotation { - def macroTransform(annottees: Any*): Any = macro jsonMacro.impl + def macroTransform(annottees: Any*): Any = macro jsonMacroInstance.impl +} + +/** + * "@jsonstrict" macro annotation for case classes + * + * Same as "@json" annotation, except that it always uses the default Play formatter. + * For example, if A is defined as: + * + * case class A(value: Int) + * + * then A(4) will be serialized as '{"value": 4}'. + */ +class jsonstrict extends StaticAnnotation { + def macroTransform(annottees: Any*): Any = macro jsonStrictMacroInstance.impl } -object jsonMacro { +class jsonMacro(isStrict: Boolean) { def impl(c: CrossVersionContext)(annottees: c.Expr[Any]*): c.Expr[Any] = { import c.universe._ @@ -40,8 +57,8 @@ object jsonMacro { def jsonFormatter(className: TypeName, fields: List[ValDef]) = { fields.length match { case 0 => c.abort(c.enclosingPosition, "Cannot create json formatter for case class with no fields") - case 1 => - // Only one field, use the serializer for the field + case 1 if !isStrict => { + // use the serializer for the field q""" implicit val jsonAnnotationFormat = { import play.api.libs.json._ @@ -51,9 +68,11 @@ object jsonMacro { ) } """ - case _ => - // More than one field, use Play's macro + } + case _ => { + // use Play's macro q"implicit val jsonAnnotationFormat = play.api.libs.json.Json.format[$className]" + } } } diff --git a/src/test/scala/com/kifi/macros/JsonFormatAnnotationTest.scala b/src/test/scala/com/kifi/macros/JsonFormatAnnotationTest.scala index cb40c18..9cf1e03 100644 --- a/src/test/scala/com/kifi/macros/JsonFormatAnnotationTest.scala +++ b/src/test/scala/com/kifi/macros/JsonFormatAnnotationTest.scala @@ -4,9 +4,11 @@ import org.specs2.mutable.Specification import play.api.libs.json._ @json case class City(name: String) - @json case class Person(name: String, age: Int) +@jsonstrict case class City2(name: String) +@jsonstrict case class Person2(name: String, age: Int) + class JsonFormatAnnotationTest extends Specification { "@json annotation" should { @@ -16,7 +18,7 @@ class JsonFormatAnnotationTest extends Specification { val city = City("San Francisco") val json = Json.toJson(city) json === JsString("San Francisco") - Json.fromJson[City](json) === JsSuccess(city) + Json.fromJson[City](json).asOpt must beSome(city) } "create correct formatter for case class with >= 2 fields" in { @@ -27,7 +29,29 @@ class JsonFormatAnnotationTest extends Specification { "name" -> "Victor Hugo", "age" -> 46 ) - Json.fromJson[Person](json) === JsSuccess(person) + Json.fromJson[Person](json).asOpt must beSome(person) + } + } + + "@jsonstrict annotation" should { + + "create correct formatter for case class with 1 field" in { + + val city = City2("San Francisco") + val json = Json.toJson(city) + json === Json.obj("name" -> "San Francisco") + Json.fromJson[City2](json).asOpt must beSome(city) + } + + "create correct formatter for case class with >= 2 fields" in { + + val person = Person2("Victor Hugo", 46) + val json = Json.toJson(person) + json === Json.obj( + "name" -> "Victor Hugo", + "age" -> 46 + ) + Json.fromJson[Person2](json).asOpt must beSome(person) } } }