I'm trying to convert Scala to JSON in the 2.1RC Play Framework.
I can do the following and get JSON:
import play.api.libs.json._
val a1=Map("val1"->"a", "val2"->"b")
Json.toJSon(a1)
Because a1 is just Map[String,String] that works OK.
But if I have something more complex like where I have Map[String,Object], that doesn't work:
val a = Map("val1" -> "xxx", "val2"-> List("a", "b", "c"))
Json.toJSon(a1)
>>> error: No Json deserializer found for type scala.collection.immutable.Map[String,Object]
I found that I can do something like the following:
val a2 = Map("val1" -> Json.toJson("a"), "val2" -> Json.toJson(List("a", "b", "c")))
Json.toJson(a2)
And that works.
But how can I do that in a general way? I thought that I could do something like the following:
a.map{ case(k,v)=> (k, Json.toJson(v) )}
>>> error: No Json deserializer found for type Object
But I still get an error that it can't be deserialized
Additional Information:
Json.toJson can convert a Map[String, String] to a JsValue:
scala> val b = Map( "1" -> "A", "2" -> "B", "3" -> "C", "4" -> "D" )
b: scala.collection.immutable.Map[String,String] = Map(1 -> A, 2 -> B, 3 -> C, 4 -> D)
scala> Json.toJson(b)
res31: play.api.libs.json.JsValue = {"1":"A","2":"B","3":"C","4":"D"}
But, it fails in trying to convert a Map[String, Object]:
scala> a
res34: scala.collection.immutable.Map[String,Object] = Map(val1 -> xxx, val2 -> List(a, b, c))
scala> Json.toJson(a)
<console>:12: error: No Json deserializer found for type scala.collection.immutable.Map[String,Object]. Try to implement an implicit Writes or Format for this type.
Json.toJson(a)
Using the 'hint' from this Play Framework page on converting Scala to Json, I found the following (http://www.playframework.org/documentation/2.0.1/ScalaJson):
If instead of Map[String, Object], there is a Map[String, JsValue], then Json.toJson() will work:
scala> val c = Map("aa" -> Json.toJson("xxxx"), "bb" -> Json.toJson( List("11", "22", "33") ) )
c: scala.collection.immutable.Map[String,play.api.libs.json.JsValue] = Map(aa -> "xxxx", bb -> ["11","22","33"])
scala> Json.toJson(c)
res36: play.api.libs.json.JsValue = {"aa":"xxxx","bb":["11","22","33"]}
So, what I would like, is that given a Map[String, Object], where I know that the Object values were all originally of type String or List[String], how to apply the function Json.toJson() to the all the values in the map and get a Map[String, JsValue].
I also found that I can filter out those values that are purely string and those that are (were) of type List[String]:
scala> val a1 = a.filter({case(k,v) => v.isInstanceOf[String]})
a1: scala.collection.immutable.Map[String,Object] = Map(val1 -> xxx)
scala> val a2 = a.filter({case(k,v) => v.isInstanceOf[List[String]]})
<console>:11: warning: non-variable type argument String in type List[String] is unchecked since it is eliminated by erasure
val a2 = a.filter({case(k,v) => v.isInstanceOf[List[String]]})
^
a2: scala.collection.immutable.Map[String,Object] = Map(val2 -> List(a, b, c))
The List[String] filtering gives a warning, but seems to give the answer I want. If the two filters could be applied and then Json.toJson() used on the values of the result, and the results combined, maybe that would work?
But the filtered results are still of type Map[String, Object] which causes a problem:
scala> Json.toJson(a1)
<console>:13: error: No Json deserializer found for type scala.collection.immutable.Map[String,Object]. Try to implement an implicit Writes or Format for this type.
Json.toJson(a1)
Play 2.1 JSON API does not provide a serializer for the Type Map[String, Ojbect].
Define case class and Format for the specific type instead of Map[String, Object]:
// { "val1" : "xxx", "val2" : ["a", "b", "c"] }
case class Hoge(val1: String, val2: List[String])
implicit val hogeFormat = Json.format[Hoge]
If you don't want to create case class.
The following code provides JSON serializer/deserializer for Map[String, Object]:
implicit val objectMapFormat = new Format[Map[String, Object]] {
def writes(map: Map[String, Object]): JsValue =
Json.obj(
"val1" -> map("val1").asInstanceOf[String],
"val2" -> map("val2").asInstanceOf[List[String]]
)
def reads(jv: JsValue): JsResult[Map[String, Object]] =
JsSuccess(Map("val1" -> (jv \ "val1").as[String], "val2" -> (jv \ "val2").as[List[String]]))
}
More dynamically
import play.api.libs.json._
import play.api.libs.json.Reads._
import play.api.libs.json.Json.JsValueWrapper
implicit val objectMapFormat = new Format[Map[String, Object]] {
def writes(map: Map[String, Object]): JsValue =
Json.obj(map.map{case (s, o) =>
val ret:(String, JsValueWrapper) = o match {
case _:String => s -> JsString(o.asInstanceOf[String])
case _ => s -> JsArray(o.asInstanceOf[List[String]].map(JsString(_)))
}
ret
}.toSeq:_*)
def reads(jv: JsValue): JsResult[Map[String, Object]] =
JsSuccess(jv.as[Map[String, JsValue]].map{case (k, v) =>
k -> (v match {
case s:JsString => s.as[String]
case l => l.as[List[String]]
})
})
}
Sample code:
val jv = Json.toJson(Map("val1" -> "xxx", "val2" -> List("a", "b", "c"), "val3" -> "sss", "val4" -> List("d", "e", "f")))
println(jv)
val jr = Json.fromJson[Map[String, Object]](jv)
println(jr.get)
The output:
> {"val1":"xxx","val2":["a","b","c"],"val3":"sss","val4":["d","e","f"]}
> Map(val1 -> xxx, val2 -> List(a, b, c), val3 -> sss, val4 -> List(d, e, f))
Related
I have a list of JsObject like:
[{
"a":"test1",
"b": 2,
"c": 5,
"errors": "Error example"
}]
I would like to get something like this a:List[Option[String]], b: List[Option[Int]] and so on. I need an option since not all the fields are alway present.
My code is:
jsObjList.map(js => {
val a = (js \ "a").asOpt[String]
val b = (js \ "b").asOpt[Int]
val c = (js \ "c").asOpt[Int]
val er= (js \ "errors").asOpt[String]
(a, b, er)
})
I read about unzip and unzip3 but I haven't found a generic function.
P.S. I am using Scala Play for the json parsing
Thanks for your help!
Class to extract values from raw JSON.
case class Foo(a: Option[String], b: Option[Int], c: Option[Int],errors: Option[String])
object Foo {
// Automatically generate json reader and writer for the class Foo
implicit val format = Json.format[Foo]
}
Keeping the implicit value in companion object of Foo will make the Scala to pick up the implicit when required automatically.
Code to parse JSON into list of case class instances
payload.validate[List[Foo]]
Use validateOpt in case you expect any parse error
payload.validateOpt[List[Foo]]
Scala REPL
scala> :paste
// Entering paste mode (ctrl-D to finish)
val str = """
[{
"a":"test1",
"b": 2,
"c": 5,
"errors": "Error example"
}]
"""
// Exiting paste mode, now interpreting.
str: String =
"
[{
"a":"test1",
"b": 2,
"c": 5,
"errors": "Error example"
}]
"
scala> val payload = Json.parse(str)
payload: play.api.libs.json.JsValue = [{"a":"test1","b":2,"c":5,"errors":"Error example"}]
scala> case class Foo(a: Option[String], b: Option[Int], c: Option[Int],errors: Option[String])
defined class Foo
scala> implicit val format = Json.format[Foo]
format: play.api.libs.json.OFormat[Foo] = play.api.libs.json.OFormat$$anon$1#53a0b0a3
scala> payload.validate[List[Foo]]
res5: play.api.libs.json.JsResult[List[Foo]] = JsSuccess(List(Foo(Some(test1),Some(2),Some(5),Some(Error example))),)
You can parse JSON as a Scala case class with a companion object containing a special val called implicit val format = Json.format[*your class*].
Here's an example similar to yours:
import play.api.libs.json.Json
val body =
"""{
| "a":"my string",
| "b": 1,
| "c": 2
|}
""".stripMargin
val body2 =
"""{
| "a":"my string",
| "c": 5
|}
""".stripMargin
case class MyClass(a: Option[String], b: Option[Int], c: Option[Int])
object MyClass {
implicit val format = Json.format[MyClass]
}
Using this, calling Json.parse(body).as[MyClass] gives:
res0: MyClass = MyClass(Some(my string),Some(2),Some(5))
Calling this Json.parse function with missing fields (assuming they are optional), such as Json.parse(body2).as[MyClass] gives:
res1: MyClass = MyClass(Some(my string),None,Some(5))
If one of the missing fields is not Optional, this parse will not work.
For example, I have this Map value in Scala:
val m = Map(
"name" -> "john doe",
"age" -> 18,
"hasChild" -> true,
"childs" -> List(
Map("name" -> "dorothy", "age" -> 5, "hasChild" -> false),
Map("name" -> "bill", "age" -> 8, "hasChild" -> false)
)
)
I want to convert it to its JSON string representation:
{
"name": "john doe",
"age": 18,
"hasChild": true,
"childs": [
{
"name": "dorothy",
"age": 5,
"hasChild": false
},
{
"name": "bill",
"age": 8,
"hasChild": false
}
]
}
I'm currenly working on Play framework v2.3, but the solution doesn't need to use Play JSON library, although it will be nice if someone can provide both Play and non-Play solution.
This is what I have done so far without success:
// using jackson library
val mapper = new ObjectMapper()
val res = mapper.writeValueAsString(m)
println(res)
Result:
{"empty":false,"traversableAgain":true}
I don't understand why I got that result.
As a non play solution, you can consider using json4s which provides a wrapper around jackson and its easy to use.
If you are using json4s then you can convert map to json just by using:
write(m)
//> res0: String = {"name":"john doe","age":18,"hasChild":true,"childs":[{"name":"dorothy","age":5,"hasChild":false},{"name":"bill","age":8,"hasChild":false}]}
--Updating to include the full example--
import org.json4s._
import org.json4s.native.Serialization._
import org.json4s.native.Serialization
implicit val formats = Serialization.formats(NoTypeHints)
val m = Map(
"name" -> "john doe",
"age" -> 18,
"hasChild" -> true,
"childs" -> List(
Map("name" -> "dorothy", "age" -> 5, "hasChild" -> false),
Map("name" -> "bill", "age" -> 8, "hasChild" -> false)))
write(m)
Output:
res0: String = {"name":"john doe","age":18,"hasChild":true,"childs":[{"name"
:"dorothy","age":5,"hasChild":false},{"name":"bill","age":8,"hasChild":false }]}
Alternative way:
import org.json4s.native.Json
import org.json4s.DefaultFormats
Json(DefaultFormats).write(m)
val mapper = new ObjectMapper()
mapper.writeValueAsString(Map("a" -> 1))
result> {"empty":false,"traversableAgain":true}
==============================
import com.fasterxml.jackson.module.scala.DefaultScalaModule
val mapper = new ObjectMapper()
mapper.registerModule(DefaultScalaModule)
mapper.writeValueAsString(Map("a" -> 1))
result> {"a":1}
You need to tell jackson how to deal with scala objects: mapper.registerModule(DefaultScalaModule)
val mymap = array.map {
case 1 => ("A", 1)
case 2 => ("B", 2)
case 3 => ("C", 3)
}
.toMap
Using scala.util.parsing.json.JSONObject, you only need 1 line:
import scala.util.parsing.json.JSONObject
JSONObject(mymap).toString()
If you're working with a well-defined data model, why not define case classes and use Play JSON macros to handle conversion? i.e.
case class Person(name: String, age: Int, hasChild: Boolean, childs: List[Person])
implicit val fmt = Json.format[Person]
val person = Person(...)
val jsonStr = Json.toJson(person)
One thing you can do using the Jackson library is to use a java HashMap object, instead of a Scala one. Then you can basically use the same "without success" code you already wrote.
import org.codehaus.jackson.map.ObjectMapper
val mapper = new ObjectMapper()
val jmap = new java.util.HashMap[String, Int]()
jmap.put("dog", 4)
jmap.put("cat", 1)
// convert to json formatted string
val jstring = mapper.writeValueAsString(jmap)
println(jstring)
returns
jstring: String = {"dog":4,"cat":1}
In case somebody is looking for a solution using standard libraries.
def toJson(query: Any): String = query match {
case m: Map[String, Any] => s"{${m.map(toJson(_)).mkString(",")}}"
case t: (String, Any) => s""""${t._1}":${toJson(t._2)}"""
case ss: Seq[Any] => s"""[${ss.map(toJson(_)).mkString(",")}]"""
case s: String => s""""$s""""
case null => "null"
case _ => query.toString
}
Given the following JSON...
{ "id":"1234",
"name" -> "joe",
"tokens: [{
"id":"1234",
"id":"2345"
}]
}
... I need to replace the value of all the ids by xxxx like this:
{ "id":"xxxx",
"name" -> "joe",
"tokens: [{
"id":"xxxx",
"id":"xxxx"
}]
}
Let's start create the JSON tree:
val json = Json.obj(
"id" -> "1234",
"name" -> "joe",
"tokens" -> Json.arr(
Json.obj("id" -> "1234"),
Json.obj("id" -> "2345")
)
)
json: play.api.libs.json.JsObject = {"id":"1234","name":"joe","tokens":[{"id":"1234"},{"id":"2345"}]}
Then, getting all the ids is very simple:
json \\ "id"
res64: Seq[play.api.libs.json.JsValue] = List("1234", "1234", "2345")
Now, how do I replace the value of all the ids by xxxx?
There doesn't appear to be a nice way to do this with the standard Play JSON library, although I'd be happy to be proved wrong in that regard. You can however do it easily using the play-json-zipper extensions:
import play.api.libs.json._
import play.api.libs.json.extensions._
val json = Json.obj(
"id" -> "1234",
"name" -> "joe",
"tokens" -> Json.arr(
Json.obj("id" -> "1234"),
Json.obj("id" -> "2345")
)
)
// Using `updateAll` we pattern match on a path (ignoring
// the existing value, as long as it's a string) and replace it
val transformed = json.updateAll {
case (__ \ "id", JsString(_)) => JsString("xxxx")
}
// play.api.libs.json.JsValue = {"id":"xxxx","name":"joe","tokens":[{"id":"xxxx"},{"id":"xxxx"}]}
To make that a re-usable function:
def replaceValue(json: JsValue, key: String, replacement: String) = json.updateAll {
case (__ \ path, JsString(_)) if path == key => JsString(replacement)
}
The json-zipper extensions are still "experimental", but if you want to add them to your project add the following to your project/Build.scala appDependencies:
"play-json-zipper" %% "play-json-zipper" % "1.0"
and the following resolver:
"Mandubian repository releases" at "https://github.com/mandubian/mandubian-mvn/raw/master/releases/"
Probably it isn't most efficient way to do it, but you can try to convert your JSON to an object copy it with new fields and then convert it back to json. Unfortunately currently I don't have environment to check the code, but it should be something like this:
case class MyId(id: String)
case class MyObject(id: String, name: String, tokens: List[MyId])
implicit val idFormat = Json.format[MyId]
implicit val objectFormat = Json.format[MyObject]
val json = Json.parse(jsonString)
val jsResult = Json.fromJson[MyObject](json)
val obj = jsResult match {
case JsSuccess(s, _) => s
case _ => throw new IllegalStateException("Unexpected")
}
val newObj = obj.copy(id = "xxxx")
val result = Json.toJson(newObj)
I am using json-spray. It seems that when I attempt to print a parsed JsString value, it includes book-ended quotes on the string.
val x1 = """ {"key1": "value1", "key2": 4} """
println(x1.asJson)
println(x1.asJson.convertTo[Map[String, JsValue]])
Which outputs:
{"key1":"value1","key2":4}
Map(key1 -> "value1", key2 -> 4)
But that means that the string value of key1 is actually quoted since scala displays strings without their quotes. i.e. val k = "value1" outputs: value1 not "value1". Perhaps I am doing something wrong, but the best I could come up with to alleviate this was the following:
val m = x1.asJson.convertTo[Map[String, JsValue]]
val z = m.map({
case(x,y) => {
val ny = y.toString( x => x match {
case v: JsString =>
v.toString().tail.init
case v =>
v.toString()
} )
(x,ny)
}})
println(z)
Which outputs a correctly displayed string:
Map(key1 -> value1, key2 -> 4)
But this solution won't work for recursively nested JSON. Is there a better workaround?
Try this:
import spray.json._
import DefaultJsonProtocol._
val jsString = new JsString("hello")
val string = jsString.convertTo[String]
In the new version, there is a little difference:
libraryDependencies ++= "io.spray" % "spray-json_2.12" % "1.3.3"
import spray.json.DefaultJsonProtocol._
import spray.json._
object SprayJson extends ExampleBase {
private def jsonValue(): Map[String, String] = {
val x1 = """ {"key1": "value1", "key2": 4} """
val json = x1.parseJson
println(json.prettyPrint)
json.convertTo[Map[String, JsValue]].map(v =>
(v._1, v._2 match {
case s: JsString => s.value
case o => o.toString()
}))
}
override def runAll(): Unit = {
println(jsonValue())
}
}
The output:
{
"key1": "value1",
"key2": 4
}
Map(key1 -> value1, key2 -> 4)
I ran into exact same problem. Digging through the source code, they are using compactPrint which seems fine. I don't at what point it is bring wrapped with quotes
I'm trying to deserialize a JsArray into a List[T] in a playframework application using Scala. After some research I found this method which is supposed to do the needed work:
/**
* Deserializer for List[T] types.
*/
implicit def listReads[T](implicit fmt: Reads[T]): Reads[List[T]] = new Reads[List[T]] {
def reads(json: JsValue) = json match {
case JsArray(ts) => ts.map(t => fromJson(t)(fmt)).toList
case _ => throw new RuntimeException("List expected")
}
}
The problem is that I didn't know how to use it. Any help is welcome.
Here's a quick example:
scala> import play.api.libs.json._
import play.api.libs.json._
scala> Json.toJson(List(1, 2, 3)).as[List[Int]]
res0: List[Int] = List(1, 2, 3)
And if you have a custom type with a Format instance:
case class Foo(i: Int, x: String)
implicit object fooFormat extends Format[Foo] {
def reads(json: JsValue) = Foo(
(json \ "i").as[Int],
(json \ "x").as[String]
)
def writes(foo: Foo) = JsObject(Seq(
"i" -> JsNumber(foo.i),
"x" -> JsString(foo.x)
))
}
It still works:
scala> val foos = Foo(1, "a") :: Foo(2, "bb") :: Nil
foos: List[Foo] = List(Foo(1,a), Foo(2,bb))
scala> val json = Json.toJson(foos)
json: play.api.libs.json.JsValue = [{"i":1,"x":"a"},{"i":2,"x":"bb"}]
scala> json.as[List[Foo]]
res1: List[Foo] = List(Foo(1,a), Foo(2,bb))
This approach would also work if your custom type had a xs: List[String] member, for example: you'd just use (json \ "xs").as[List[String]] in your reads method and Json.toJson(foo.xs) in your writes.