3

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?

Mario Galic
  • 41,266
  • 6
  • 39
  • 76
Matt Miller
  • 3,411
  • 5
  • 24
  • 26
  • 1
    You could do is write your own (implicit?) conversion from `org.json4s.JValue` to (and/or from ) `play.api.libs.json.JsValue`. Shouldn't be too hard. – mfirry Nov 02 '16 at 21:00
  • I'm just curious, what do validation/transformation do you need that Play JSON doesn't provide with it's own validation and JSON transformers? – Michael Zajac Nov 02 '16 at 21:02

1 Answers1

5

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)))))))
Paweł Prażak
  • 2,819
  • 1
  • 23
  • 39
pamu
  • 14,066
  • 2
  • 14
  • 35