scalaplayframeworkjson4splay-json

How can I convert between play.api.libs.json.JsValue and org.json4s.JValue


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?


Solution

  • Pattern match and convert

    Write implicit conversions from one to other.

    Pattern match and convert

    1. JsString to JString

    2. JsNull to JNull

    3. JsNumber to JNumber

    4. 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)))))))