Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ lazy val root = project
`play-jsonJVM`,
`play-functionalJS`,
`play-functionalJVM`,
`play-json-joda`
`play-json-joda`,
`play-json4s`
).settings(
commonSettings,
publishTo := None
Expand Down Expand Up @@ -190,6 +191,17 @@ lazy val `play-json-joda` = project
)
.dependsOn(`play-jsonJVM`)

lazy val `play-json4s` = project
.in(file("play-json4s"))
.enablePlugins(PlayLibrary)
.settings(commonSettings ++ playJsonMimaSettings ++ Seq(
libraryDependencies ++= Seq(
"org.json4s" %% "json4s-core" % "3.6.3",
"com.chuusai" %% "shapeless" % "2.3.3" % Test
) ++ specsBuild.value.map(_ % Test)
))
.dependsOn(`play-jsonJVM`)

lazy val `play-jsonJVM` = `play-json`.jvm.
settings(
libraryDependencies ++=
Expand Down
122 changes: 122 additions & 0 deletions play-json4s/src/main/scala/Formats.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package play.api.libs.json.json4s

import scala.util.control.NonFatal

import org.json4s.{ JValue, Reader, Writer }

import play.api.libs.json.{
JsError,
JsResult,
JsSuccess,
JsValue,
Reads,
Writes
}

object Formats extends Formats

sealed trait Formats extends LowPriorityFormats {
/**
* {{{
* def test[T](implicit w: Reads[T]): Reader[T] = Formats.reader[T]
* }}}
*/
implicit def reader[T](implicit reads: Reads[T], conv: JValue => JsValue = JValueConverters.jvalue2JsValue): Reader[T] = new JsValueReader[T](reads, conv)

/**
* {{{
* def test[T](implicit w: Writes[T]): Writer[T] = Formats.defaultWriter[T]
* }}}
*/
implicit def defaultWriter[T](implicit writes: Writes[T]): Writer[T] =
new JsValueWriter[T](writes, JValueConverters.jsvalue2JValue)

/**
* {{{
* def test[T](implicit r: Reader[T]): Reads[T] = Formats.reads[T]
* }}}
*/
implicit def reads[T](implicit reader: Reader[T], conv: JsValue => JValue): Reads[T] = reads[T]((_: JsValue, cause: Throwable) => JsError(cause.getMessage))

/**
* {{{
* def test[T](implicit r: Reader[T]): Reads[T] =
* Formats.reads[T](err = { (v: JsValue, cause: Throwable) =>
* JsError(s"Custom message: \$v; \$cause")
* })
* }}}
*/
def reads[T](err: (JsValue, Throwable) => JsError)(implicit reader: Reader[T], conv: JsValue => JValue): Reads[T] = new JValueReads[T](reader, conv, err)

/**
* {{{
* def test[T](implicit w: Writer[T]): Writes[T] = Formats.defaultWrites[T]
* }}}
*/
implicit def defaultWrites[T](implicit writer: Writer[T]): Writes[T] =
new JValueWrites[T](writer, JValueConverters.jvalue2JsValue)

}

private[json4s] sealed trait LowPriorityFormats { _: Formats =>
/**
* {{{
* def test[T](implicit w: Reads[T]): Reader[T] = Formats.defaultReader[T]
* }}}
*/
implicit def defaultReader[T](implicit reads: Reads[T]): Reader[T] =
new JsValueReader[T](reads, JValueConverters.jvalue2JsValue)

/**
* {{{
* def test[T](implicit w: Writes[T]): Writer[T] = Formats.writer[T]
* }}}
*/
implicit def writer[T](implicit writes: Writes[T], conv: JsValue => JValue): Writer[T] = new JsValueWriter[T](writes, conv)

/**
* {{{
* def test[T](implicit r: Reader[T]): Reads[T] = Formats.defaultReads[T]
* }}}
*/
implicit def defaultReads[T](implicit reader: Reader[T]): Reads[T] =
reads[T]((_: JsValue, cause: Throwable) => JsError(cause.getMessage))(
reader, JValueConverters.jsvalue2JValue)

/**
* {{{
* def test[T](implicit w: Writer[T]): Writes[T] = Formats.writes[T]
* }}}
*/
implicit def writes[T](implicit writer: Writer[T], conv: JValue => JsValue): Writes[T] = new JValueWrites[T](writer, conv)
}

private[json4s] final class JsValueReader[T](
r: Reads[T], conv: JValue => JsValue) extends Reader[T] {

def read(value: JValue): T = r.reads(conv(value)).get
}

private[json4s] final class JsValueWriter[T](
w: Writes[T], conv: JsValue => JValue) extends Writer[T] {

def write(value: T): JValue = conv(w writes value)
}

private[json4s] final class JValueWrites[T](
w: Writer[T],
conv: JValue => JsValue) extends Writes[T] {
def writes(value: T): JsValue = conv(w write value)
}

private[json4s] final class JValueReads[T](
r: Reader[T],
conv: JsValue => JValue,
err: (JsValue, Throwable) => JsError) extends Reads[T] {
def reads(js: JsValue): JsResult[T] = try {
val v = r.read(conv(js))
JsSuccess(v)
} catch {
case NonFatal(cause) => err(js, cause)
}
}
130 changes: 130 additions & 0 deletions play-json4s/src/main/scala/JValueConverters.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package play.api.libs.json.json4s

import scala.language.implicitConversions

import org.json4s.{
JArray,
JBool,
JDecimal,
JDouble,
JField,
JInt,
JLong,
JNothing,
JNull,
JObject,
JSet,
JString,
JValue
}

import play.api.libs.json.{
JsArray,
JsBoolean,
JsFalse,
JsNumber,
JsNull,
JsObject,
JsString,
JsTrue,
JsValue
}

/**
* {{{
* import play.api.libs.json.json4s.JValueConverters
*
* val jsTrue: JsBoolean = JBool.True
* val jbool: JBool = JsTrue
* }}}
*/
object JValueConverters extends LowPriorityJValueImplicits {
implicit val jnull2JsNull: JNull.type => JsNull.type = _ => JsNull

implicit val jsnull2JNull: JsNull.type => JNull.type = _ => JNull

implicit val jnothing2JsNull: JNothing.type => JsNull.type = _ => JsNull

implicit def jbool2JsBoolean(jb: JBool): JsBoolean = jb match {
case JBool.True => JsTrue
case _ => JsFalse
}

implicit val jstrue2JBool: JsTrue.type => JBool = _ => JBool.True

implicit val jsfalse2JBool: JsFalse.type => JBool = _ => JBool.False

implicit def jsbool2JBool(jb: JsBoolean): JBool = jb match {
case JsTrue => JBool.True
case _ => JBool.False
}

implicit def jstring2JsString(js: JString): JsString = JsString(js.s)

implicit def jsstring2Jtring(js: JsString): JString = JString(js.value)

implicit def jdecimal2JsNumber(jd: JDecimal): JsNumber = JsNumber(jd.num)

implicit def jsnumber2JDecimal(jn: JsNumber): JDecimal = JDecimal(jn.value)

implicit def jint2JsNumber(ji: JInt): JsNumber = JsNumber(BigDecimal(ji.num))

implicit def jdouble2JsNumber(ji: JDouble): JsNumber =
JsNumber(BigDecimal(ji.num))

implicit def jlong2JsNumber(ji: JLong): JsNumber =
JsNumber(BigDecimal(ji.num))

implicit def jfield2Field(jf: JField): (String, JsValue) =
jf._1 -> jvalue2JsValue(jf._2)

implicit def field2JField(jf: (String, JsValue)): JField =
jf._1 -> jsvalue2JValue(jf._2)

implicit def jobject2JsObject(jo: JObject): JsObject =
JsObject(jo.obj.map({
case (nme, jv) => nme -> jvalue2JsValue(jv)
})(scala.collection.breakOut))

implicit def jsobject2JObject(jo: JsObject): JObject =
JObject(jo.fields.toList.map(field2JField))

implicit def jarray2JsArray(ja: JArray): JsArray =
JsArray(ja.arr.map(jvalue2JsValue))

implicit def jsarray2JArray(ja: JsArray): JArray =
JArray(ja.value.toList.map(jsvalue2JValue))

implicit def jset2JsArray(js: JSet): JsArray =
JsArray(js.set.toList.map(jvalue2JsValue))
}

private[json4s] sealed trait LowPriorityJValueImplicits {
_: JValueConverters.type =>

implicit def jvalue2JsValue(jv: JValue): JsValue = jv match {
case a @ JArray(_) => jarray2JsArray(a)
case JDouble(d) => JsNumber(BigDecimal(d))
case JInt(i) => JsNumber(BigDecimal(i))
case JLong(l) => JsNumber(BigDecimal(l))
case JNull => JsNull
case JNothing => JsNull
case JBool.True => JsTrue
case JBool.False => JsFalse
case JBool(b) => JsBoolean(b)
case s @ JSet(_) => jset2JsArray(s)
case JString(s) => JsString(s)
case JDecimal(n) => JsNumber(n)
case o @ JObject(fs) => jobject2JsObject(o)
}

implicit def jsvalue2JValue(jv: JsValue): JValue = jv match {
case a @ JsArray(_) => jsarray2JArray(a)
case JsNull => JNull
case JsTrue => JBool.True
case JsFalse => JBool.False
case JsString(s) => JString(s)
case JsNumber(n) => JDecimal(n)
case o @ JsObject(_) => jsobject2JObject(o)
}
}
Loading