From 1b4cec12b015badc4e20fae45276a50781d01334 Mon Sep 17 00:00:00 2001 From: xuwei-k <6b656e6a69@gmail.com> Date: Wed, 7 Jun 2017 23:44:05 +0900 Subject: [PATCH] add boilerplate methods for case class Format sbt/contraband support auto generate sjsonnew Formats, it's useful. but I want to convenient methods like this. because there are times when I don't want to use contraband. The API almost same as argonaut.CodecJsons, asProductN in sbinary, and my library(play-json-extras) - https://github.com/sbt/contraband/blob/v0.3.0-M5/docs/03-json.md - https://static.javadoc.io/io.argonaut/argonaut_2.12/6.2/argonaut/CodecJsons.html - https://github.com/argonaut-io/argonaut/blob/v6.2/project/Boilerplate.scala - https://github.com/sbt/sbinary/blob/v0.4.4/core/src/generic.scala#L111-L134 - https://github.com/xuwei-k/play-json-extra - http://d.hatena.ne.jp/xuwei/20140402/1396408213 --- .../sjsonnew/CaseClassFormats.scala.template | 82 +++++++++++++++++++ .../scala/sjsonnew/BasicJsonProtocol.scala | 1 + .../support/spray/CaseClassFormatSpec.scala | 57 +++++++++++++ 3 files changed, 140 insertions(+) create mode 100644 core/src/main/boilerplate/sjsonnew/CaseClassFormats.scala.template create mode 100644 support/spray/src/test/scala/sjsonnew/support/spray/CaseClassFormatSpec.scala diff --git a/core/src/main/boilerplate/sjsonnew/CaseClassFormats.scala.template b/core/src/main/boilerplate/sjsonnew/CaseClassFormats.scala.template new file mode 100644 index 0000000..02814ad --- /dev/null +++ b/core/src/main/boilerplate/sjsonnew/CaseClassFormats.scala.template @@ -0,0 +1,82 @@ +/* + * Original implementation (C) 2009-2011 Debasish Ghosh + * Adapted and extended in 2011 by Mathias Doenitz + * Adapted and extended in 2016 by Eugene Yokota + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sjsonnew + +trait CaseClassFormats { + + // simple alias for reduced verbosity + private[this] type JF[A] = JsonFormat[A] + private[this] type K[A] = JsonKeyWriter[A] + + + [2..22#def caseClass[[#K1: K#], [#A1: JF#], Z](applyFunc: ([#A1#]) => Z, unapplyFunc: Z => Option[([#A1#])])([#key1: K1#]): JF[Z] = + caseClass1[[#K1#], [#A1#], Z](applyFunc, unapplyFunc)([#key1#]) + + def caseClass1[[#K1#], [#A1#], Z](applyFunc: ([#A1#]) => Z, unapplyFunc: Z => Option[([#A1#])])([#key1: K1#])(implicit [#A1: JF[A1]#], [#K1: K[K1]#]): JF[Z] = new JsonFormat[Z] { + def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): Z = jsOpt match { + case Some(js) => + unbuilder.beginObject(js) + val z = applyFunc( + [#unbuilder.readField[A1](K1.write(key1))#] + ) + unbuilder.endObject() + z + case None => + deserializationError("Expected JsObject but found None") + } + + def write[J](obj: Z, builder: Builder[J]): Unit = { + val t = unapplyFunc(obj).get + builder.beginObject() + [#builder.addField(K1.write(key1), t._1)# + ] + builder.endObject() + } + } + + def caseClassArray1[[#A1#], Z](applyFunc: ([#A1#]) => Z, unapplyFunc: Z => Option[([#A1#])])(implicit [#A1: JF[A1]#]): JF[Z] = { + new JsonFormat[Z] { + def write[J](t: Z, builder: Builder[J]): Unit = { + val x = unapplyFunc(t).get + builder.beginArray() + [#A1.write(x._1, builder)# + ] + builder.endArray() + } + + def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): Z = + jsOpt match { + case Some(js) => + unbuilder.beginArray(js) + [#val a1 = unbuilder.nextElement# + ] + val xs = applyFunc([#A1.read(Some(a1), unbuilder)#]) + unbuilder.endArray() + xs + case None => + applyFunc([#A1.read(None, unbuilder)#]) + } + } + } + + def caseClassArray[[#A1: JF#], Z](applyFunc: ([#A1#]) => Z, unapplyFunc: Z => Option[([#A1#])]): JF[Z] = + caseClassArray1[[#A1#], Z](applyFunc, unapplyFunc) + # + ] +} diff --git a/core/src/main/scala/sjsonnew/BasicJsonProtocol.scala b/core/src/main/scala/sjsonnew/BasicJsonProtocol.scala index d497796..27944db 100644 --- a/core/src/main/scala/sjsonnew/BasicJsonProtocol.scala +++ b/core/src/main/scala/sjsonnew/BasicJsonProtocol.scala @@ -34,5 +34,6 @@ trait BasicJsonProtocol with JavaExtraFormats with CalendarFormats with ImplicitHashWriters + with CaseClassFormats object BasicJsonProtocol extends BasicJsonProtocol diff --git a/support/spray/src/test/scala/sjsonnew/support/spray/CaseClassFormatSpec.scala b/support/spray/src/test/scala/sjsonnew/support/spray/CaseClassFormatSpec.scala new file mode 100644 index 0000000..864f40e --- /dev/null +++ b/support/spray/src/test/scala/sjsonnew/support/spray/CaseClassFormatSpec.scala @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2011 Mathias Doenitz + * Adapted and extended in 2016 by Eugene Yokota + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sjsonnew +package support.spray + +import spray.json.{JsonFormat => _, _} +import org.specs2.mutable._ + +class CaseClassFormatsSpec extends Specification with BasicJsonProtocol { + + private case class Foo(a: Int, b: String) + private val foo = Foo(42, "bar") + + "case class Json Object Format" should { + implicit val instance: JsonFormat[Foo] = + BasicJsonProtocol.caseClass(Foo.apply _, Foo.unapply _)("a", "b") + + val json = JsObject("a" -> JsNumber(42), "b" -> JsString("bar")) + + "convert to a JsObject" in { + Converter.toJsonUnsafe(foo) mustEqual json + } + "be able to convert a JsObject to a case class" in { + Converter.fromJsonUnsafe[Foo](json) mustEqual foo + } + } + + "case class Json Array Format" should { + implicit val instance: JsonFormat[Foo] = + BasicJsonProtocol.caseClassArray(Foo.apply _, Foo.unapply _) + + val json = JsArray(JsNumber(42), JsString("bar")) + + "convert to a JsArray" in { + Converter.toJsonUnsafe(foo) mustEqual json + } + "be able to convert a JsArray to a case class" in { + Converter.fromJsonUnsafe[Foo](json) mustEqual foo + } + } + +}