I'm using Play Framework to build an API, but need to perform some validation and transformation operations with a library that only speaks Json4s.
So far, the only thing I've been able to get to work is converting to a string and parsing with the target library:
import org.json4s._
import org.json4s.jackson.JsonMethods._
import play.api.libs.json._
val playJson: JsValue = Json.parse(compact(render(json4sJson)))
val json4sJson: JValue = parse(Json.stringify(playJson))
But that's obviously inefficient and makes for ugly code. Is there a better way?
Write implicit conversions from one to other.
Pattern match and convert
JsString to JString
JsNull to JNull
JsNumber to JNumber
JsArray to JArray ... so on
Use ALT + ENTER feature of Intellij (in case you are using it) to automatically get all case object and case class for pattern matching (without manually checking the source code).
Recursively convert in case of JsArray and JsObject conversion into JArray and JObject correspondingly.
import play.api.libs.{ json => pjson }
import org.{ json4s => j4s }
object Conversions {
implicit def toJson4s(json: play.api.libs.json.JsValue):org.json4s.JValue = json match {
case pjson.JsString(str) => j4s.JString(str)
case pjson.JsNull => j4s.JNull
case pjson.JsBoolean(value) => j4s.JBool(value)
case pjson.JsNumber(value) => j4s.JDecimal(value)
case pjson.JsArray(items) => j4s.JArray(items.map(toJson4s(_)).toList)
case pjson.JsObject(items) => j4s.JObject(items.map { case (k, v) => k -> toJson4s(v)}.toList)
}
implicit def toPlayJson(json: org.json4s.JValue): play.api.libs.json.JsValue = json match {
case j4s.JString(str) => pjson.JsString(str)
case j4s.JNothing => pjson.JsNull
case j4s.JNull => pjson.JsNull
case j4s.JDecimal(value) => pjson.JsNumber(value)
case j4s.JDouble(value) => pjson.JsNumber(value)
case j4s.JInt(value) => pjson.JsNumber(BigDecimal(value))
case j4s.JLong(value) => pjson.JsNumber(BigDecimal(value))
case j4s.JBool(value) => pjson.JsBoolean(value)
case j4s.JSet(fields) => pjson.JsArray(fields.toList.map(toPlayJson(_)))
case j4s.JArray(fields) => pjson.JsArray(fields.map(toPlayJson(_)))
case j4s.JObject(fields) => pjson.JsObject(fields.map { case (k, v) => k -> toPlayJson(v)}.toMap)
}
}
When ever you want to convert one to other, then Just import conversions object
import Conversions._
Scala REPL
scala> object Conversions {
|
| implicit def toJson4s(json: play.api.libs.json.JsValue):org.json4s.JValue = json match {
| case pjson.JsString(str) => j4s.JString(str)
| case pjson.JsNull => j4s.JNull
| case pjson.JsBoolean(value) => j4s.JBool(value)
| case pjson.JsNumber(value) => j4s.JDecimal(value)
| case pjson.JsArray(items) => j4s.JArray(items.map(toJson4s(_)).toList)
| case pjson.JsObject(items) => j4s.JObject(items.map { case (k, v) => k -> toJson4s(v)}.toList)
| }
|
| implicit def toPlayJson(json: org.json4s.JValue): play.api.libs.json.JsValue = json match {
| case j4s.JString(str) => pjson.JsString(str)
| case j4s.JNothing => pjson.JsNull
| case j4s.JNull => pjson.JsNull
| case j4s.JDecimal(value) => pjson.JsNumber(value)
| case j4s.JDouble(value) => pjson.JsNumber(value)
| case j4s.JInt(value) => pjson.JsNumber(BigDecimal(value))
| case j4s.JLong(value) => pjson.JsNumber(BigDecimal(value))
| case j4s.JBool(value) => pjson.JsBoolean(value)
| case j4s.JArray(fields) => pjson.JsArray(fields.map(toPlayJson(_)))
| case j4s.JObject(fields) => pjson.JsObject(fields.map { case (k, v) => k -> toPlayJson(v)}.toMap)
| }
|
| }
warning: there were two feature warnings; re-run with -feature for details
defined object Conversions
scala> import Conversions._
import Conversions._
scala> val json = pjson.Json.parse("""{"name": "pamu", "age": 1}""")
json: play.api.libs.json.JsValue = {"name":"pamu","age":1}
scala> toJson4s(json)
res0: org.json4s.JValue = JObject(List((name,JString(pamu)), (age,JDecimal(1))))
More nested Json
scala> val payload = pjson.Json.parse("""
| {
| "firstName": "John",
| "lastName": "Smith",
| "isAlive": true,
| "age": 25,
| "address": {
| "streetAddress": "21 2nd Street",
| "city": "New York",
| "state": "NY",
| "postalCode": "10021-3100"
| },
| "phoneNumbers": [
| {
| "type": "home",
| "number": "212 555-1234"
| },
| {
| "type": "office",
| "number": "646 555-4567"
| },
| {
| "type": "mobile",
| "number": "123 456-7890"
| }
| ],
| "children": [],
| "spouse": null
| }
| """)
payload: play.api.libs.json.JsValue = {"firstName":"John","lastName":"Smith","isAlive":true,"age":25,"address":{"streetAddress":"21 2nd Street","city":"New York","state":"NY","postalCode":"10021-3100"},"phoneNumbers":[{"type":"home","number":"212 555-1234"},{"type":"office","number":"646 555-4567"},{"type":"mobile","number":"123 456-7890"}],"children":[],"spouse":null}
scala> toJson4s(payload)
res1: org.json4s.JValue = JObject(List((children,JArray(List())), (isAlive,JBool(true)), (spouse,JNull), (phoneNumbers,JArray(List(JObject(List((type,JString(home)), (number,JString(212 555-1234)))), JObject(List((type,JString(office)), (number,JString(646 555-4567)))), JObject(List((type,JString(mobile)), (number,JString(123 456-7890))))))), (age,JDecimal(25)), (lastName,JString(Smith)), (firstName,JString(John)), (address,JObject(List((streetAddress,JString(21 2nd Street)), (city,JString(New York)), (state,JString(NY)), (postalCode,JString(10021-3100)))))))