In Scala, how can I de-serialize a JSON string, modify a value and serialize back to string?
There has to be a way to do this without using 3rd party libraries but I can't get it to work. Here is what I tried so far:
import scala.util.parsing.json
var lines = "{\"id\" : \"abc\", \"stuff\" : [1, 2, 3], \"more\" : {\"bro\" : \"science\"}}"
// Test 1
val myJSON = json.JSON.parseRaw(lines)
// myJSON: Option[scala.util.parsing.json.JSONType] = Some({"id" : "abc", "stuff" : [1.0, 2.0, 3.0], "more" : {"bro" : "science"}})
// I cannot modify fields on the JSONType instance but toString() works well.
// res1: String = Some({"id" : "abc", "stuff" : [1.0, 2.0, 3.0], "more" : {"bro" : "science"}})
// Test 2
// This way I can parse JSON into a map and manipulate its values.
// val myMap = json.JSON.parseFull(lines).get.asInstanceOf[Map[String, Any]] + ("id" -> "blah")
// myMap: scala.collection.immutable.Map[String,Any] = Map(id -> blah, stuff -> List(1.0, 2.0, 3.0), more -> Map(bro -> science))
// However, when converted to an instance of JSONObject and calling
// toString() only the top-level items are JSON-serialized
new json.JSONObject(myMap).toString()
// res2: String = {"id" : "blah", "stuff" : List(1.0, 2.0, 3.0), "more" : Map(bro -> science)}
If it's not possible with standard Scala, I'd appreciate and example of how to do this with a third party library.
Thanks,
/David
small silly/trivial example of what I mentioned. Could be written better too but wanted to break it into chunks. There is a lot you can do with them:
Here is old link in terms of play version but as far as I know up to date on features available in 2.3.x:
https://www.playframework.com/documentation/2.1.1/ScalaJsonTransformers
import play.api.libs.json._
var lines = "{\"id\" : \"abc\", \"stuff\" : [1, 2, 3], \"more\" : {\"bro\" : \"science\"}}"
val jsonAsJsValue = Json.parse(lines)
//jsonAsJsValue: play.api.libs.json.JsValue = {"id":"abc","stuff": [1,2,3],"more":{"bro":"science"}}
val updateIdTransformer = (__ \"id").json.update(
__.read[JsString].map{a => JsString("def")}
)
val updatedJson = jsonAsJsValue.transform(updateIdTransformer)
//updatedJson: play.api.libs.json.JsResult[play.api.libs.json.JsObject] = JsSuccess({"id":"def","stuff":[1,2,3],"more":{"bro":"science"}},/id)
Related
I'm very new to Argonaut (which I'm forced to use since the codebase is older, using an old version of scalaz, and we have no intentions to update it since we're rewriting this outdated code from scratch), and I'm trying to find a way to dump some data into JSON.
I have an case class called Magnitude. For simplicity, define it as:
case class Magnitude(bandname: String)
What I want is a function that takes in a List[Magnitude] and outputs Json that I can chain together with other Json.
Right now, I'm doing:
def magnitudeFields(magnitudes: List[Magnitude]): Json =
magnitudes.foldLeft(jEmptyObject) { case (j, m) =>
("bandName" := m.bandname) ->: j
}
and then say I have an instantiation of List[Magnitude]:
val magList = List(Magnitude("R"), Magnitude("J"), Magnitude("_uc"), Magnitude("K"))
when I call magnitudeFields(magList) in a call like:
... previousJsonData ->: ('magnitudes' := magnitudeFields(magList)) ->: nextJsonData ...
I only receive the bandName: K and the other three preceding entries are not part of the JSON output by the function:
"magnitudes" : {
"bandName" : "K"
},
instead of what I want, which is:
"magnitudes" : {
"bandName" : "R"
"bandName" : "J"
"bandName" : "_uc"
"bandName" : "K"
},
Any help would be greatly appreciated. I suspect I'm doing something wrong by using jEmptyObject as the default to the foldLeft and may need to use something like jArray at some point (with the function returning List[Json] instead, but I'm not sure how I should be doing this.
You should just define an EncodeJson instance for Magnitude and let Argonaut deal with the rest of the logic:
import argonaut._, Argonaut._
case class Magnitude(bandname: String)
object Magnitude {
implicit val encode: EncodeJson[Magnitude] =
EncodeJson { m =>
jSingleObject("bandName", jString(m.bandname))
}
}
object Main {
val magList = List(Magnitude("R"), Magnitude("J"), Magnitude("_uc"), Magnitude("K"))
def main(args: Array[String]): Unit = {
val prefix = "prefix" := "prefix"
val suffix = "suffix" := "suffix"
val json = prefix ->: ("magnitudes" := magList) ->: suffix ->: jEmptyObject
println(json.asJson.spaces2)
//{
// "prefix" : "prefix",
// "magnitudes" : [
// {
// "bandName" : "R"
// },
// {
// "bandName" : "J"
// },
// {
// "bandName" : "_uc"
// },
// {
// "bandName" : "K"
// }
// ],
// "suffix" : "suffix"
//}
}
}
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
}
Is there a straight forward way to format a JSON string in scala?
I have a JSON String like this:
val json = {"foo": {"bar": {"baz": T}}}
Can I use a function f such that:
f(json) = {
"foo":
{"bar":
{"baz": T}
}
}
I know the formatting I have done in my answer is no perfect, but you get the point. And yes, can it be done without using Play Framework?
In case you are using Play Framework you could use Json.prettyPrint method to format JsValue:
import play.api.libs.json.Json
val str = """{"foo": {"bar": {"baz": "T"}}}"""
val jsValue = Json parse str
// JsValue = {"foo":{"bar":{"baz":"T"}}}
Json prettyPrint jsValue
// String =
// {
// "foo" : {
// "bar" : {
// "baz" : "T"
// }
// }
// }
In case you are using scala.util.parsing.json you have to create such method by yourself. For instance:
def format(t: Any, i: Int = 0): String = t match {
case o: JSONObject =>
o.obj.map{ case (k, v) =>
" "*(i+1) + JSONFormat.defaultFormatter(k) + ": " + format(v, i+1)
}.mkString("{\n", ",\n", "\n" + " "*i + "}")
case a: JSONArray =>
a.list.map{
e => " "*(i+1) + format(e, i+1)
}.mkString("[\n", ",\n", "\n" + " "*i + "]")
case _ => JSONFormat defaultFormatter t
}
val jsn = JSON.parseRaw("""{"foo": {"bar": {"baz": "T"}, "arr": [1, 2, "x"]}, "foo2": "a"}""").get
// JSONType = {"foo" : {"bar" : {"baz" : "T"}, "arr" : [1.0, 2.0, "x"]}, "foo2" : "a"}
format(jsn)
// String =
// {
// "foo": {
// "bar": {
// "baz": "T"
// },
// "arr": [
// 1.0,
// 2.0,
// "x"
// ]
// },
// "foo2": "a"
// }
Note that this is not an efficient implementation.
I thought I read somewhere that Typesafe was considering separating their JSON processing out of Play, so look into that to apply #senia's solution first.
Otherwise, look at Jackson--or more precisely, the Scala wrapper for Jackson:
val mapper = new ObjectMapper()
mapper.registerModule(DefaultScalaModule)
val writer = mapper.writerWithDefaultPrettyPrinter
val json = writer.writeValueAsString(Object value)
I've also heard that the kids are really into Scala Pickling, which apparently has pretty printing as well.
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)
Is there a straight forward way to format a JSON string in scala?
I have a JSON String like this:
val json = {"foo": {"bar": {"baz": T}}}
Can I use a function f such that:
f(json) = {
"foo":
{"bar":
{"baz": T}
}
}
I know the formatting I have done in my answer is no perfect, but you get the point. And yes, can it be done without using Play Framework?
In case you are using Play Framework you could use Json.prettyPrint method to format JsValue:
import play.api.libs.json.Json
val str = """{"foo": {"bar": {"baz": "T"}}}"""
val jsValue = Json parse str
// JsValue = {"foo":{"bar":{"baz":"T"}}}
Json prettyPrint jsValue
// String =
// {
// "foo" : {
// "bar" : {
// "baz" : "T"
// }
// }
// }
In case you are using scala.util.parsing.json you have to create such method by yourself. For instance:
def format(t: Any, i: Int = 0): String = t match {
case o: JSONObject =>
o.obj.map{ case (k, v) =>
" "*(i+1) + JSONFormat.defaultFormatter(k) + ": " + format(v, i+1)
}.mkString("{\n", ",\n", "\n" + " "*i + "}")
case a: JSONArray =>
a.list.map{
e => " "*(i+1) + format(e, i+1)
}.mkString("[\n", ",\n", "\n" + " "*i + "]")
case _ => JSONFormat defaultFormatter t
}
val jsn = JSON.parseRaw("""{"foo": {"bar": {"baz": "T"}, "arr": [1, 2, "x"]}, "foo2": "a"}""").get
// JSONType = {"foo" : {"bar" : {"baz" : "T"}, "arr" : [1.0, 2.0, "x"]}, "foo2" : "a"}
format(jsn)
// String =
// {
// "foo": {
// "bar": {
// "baz": "T"
// },
// "arr": [
// 1.0,
// 2.0,
// "x"
// ]
// },
// "foo2": "a"
// }
Note that this is not an efficient implementation.
I thought I read somewhere that Typesafe was considering separating their JSON processing out of Play, so look into that to apply #senia's solution first.
Otherwise, look at Jackson--or more precisely, the Scala wrapper for Jackson:
val mapper = new ObjectMapper()
mapper.registerModule(DefaultScalaModule)
val writer = mapper.writerWithDefaultPrettyPrinter
val json = writer.writeValueAsString(Object value)
I've also heard that the kids are really into Scala Pickling, which apparently has pretty printing as well.