I converted a Scala map to JSON using JSONObject(map.toMap). How do I decode it to get the map back? Also, is there any better way to enocode Scala Map to JSON and decode it?
scala.util.parsing.json.JSONObject has an obj method that returns a Map[String, Any].
scala> val map = Map("zero" -> 0, "one" -> 1, "two" -> 2)
map: scala.collection.immutable.Map[String,Int] = Map(zero -> 0, one -> 1, two -> 2)
scala> scala.util.parsing.json.JSONObject(map)
res0: scala.util.parsing.json.JSONObject = {"zero" : 0, "one" : 1, "two" : 2}
scala> res0.obj
res1: Map[String,Any] = Map(zero -> 0, one -> 1, two -> 2)
You could use the parse method in the Jerkson library:
import com.codahale.jerkson.Json._
parse[Map[String,Any]](JSONObject(map.toMap).toString)
Related
I have input json like
{"a": "x", "b": "y", "c": "z", .... }
I want to convert this json to a Map like Map[String, String]
so basically a map of key value pairs.
How can I do this using circe?
Note that I don't know what keys "a", "b", "c" will be present in Json. All I know is that they will always be strings and never any other data type.
I looked at Custom Decoders here https://circe.github.io/circe/codecs/custom-codecs.html but they work only when you know the tag names.
I found an example to do this in Jackson. but not in circe
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.databind.ObjectMapper
val data = """
{"a": "x", "b", "y", "c": "z"}
"""
val mapper = new ObjectMapper
mapper.registerModule(DefaultScalaModule)
mapper.readValue(data, classOf[Map[String, String]])
While the solutions in the other answer work, they're much more verbose than necessary. Off-the-shelf circe provides an implicit Decoder[Map[String, String]] instance, so you can just write the following:
scala> val doc = """{"a": "x", "b": "y", "c": "z"}"""
doc: String = {"a": "x", "b": "y", "c": "z"}
scala> io.circe.parser.decode[Map[String, String]](doc)
res0: Either[io.circe.Error,Map[String,String]] = Right(Map(a -> x, b -> y, c -> z))
The Decoder[Map[String, String]] instance is defined in the Decoder companion object, so it's always available—you don't need any imports, other modules, etc. Circe provides instances like this for most standard library types with reasonable instances. If you want to decode a JSON array into a List[String], for example, you don't need to build your own Decoder[List[String]]—you can just use the one in implicit scope that comes from the Decoder companion object.
This isn't just a less verbose way to solve this problem, it's the recommended way to solve it. Manually constructing an explicit decoder instance and converting from Either to Try to compose parsing and decoding operations is both unnecessary and error-prone (if you do need to end up with Try or Option or whatever, it's almost certainly best to do that at the end).
Assuming:
val rawJson: String = """{"a": "x", "b": "y", "c": "z"}"""
This works:
import io.circe.parser._
val result: Try[Map[String, String]] = parse(rawJson).toTry
.flatMap(json => Try(json.asObject.getOrElse(sys.error("Not a JSON Object"))))
.flatMap(jsonObject => Try(jsonObject.toMap.map{case (name, value) => name -> value.asString.getOrElse(sys.error(s"Field '$name' is not a JSON string"))}))
val map: Map[String, String] = result.get
println(map)
Or with using Decoder:
import io.circe.Decoder
val decoder = Decoder.decodeMap(KeyDecoder.decodeKeyString, Decoder.decodeString)
val result = for {
json <- parse(rawJson).toTry
map <- decoder.decodeJson(json).toTry
} yield map
val map = result.get
println(map)
You can test following invalid inputs and see, what exception will be thrown:
val rawJson: String = """xxx{"a": "x", "b": "y", "c": "z"}""" // invalid JSON
val rawJson: String = """[1,2,3]""" // not a JSON object
val rawJson: String = """{"a": 1, "b": "y", "c": "z"}""" // not all values are string
I'm new in Scala and do not know how to deal with this Json with json4s:
After parsing the json and extracting the following json through:
val data = json \\ "someKey"
I have a Json like this:
[{"Id":14706061,
"Rcvr":1,
"HasSig":true,
"Sig":80},
{"Id":3425490,
"Rcvr":1,
"HasSig":false,
"Sig": 80}]
Printing it to the console, it returns:
JArray(List(JObject(List((Id,JInt(14706061)), (Rcvr,JInt(1)), (HasSig,JBool(true)), (Sig,JInt(80), Id,JInt(3425490)), (Rcvr,JInt(1)), (HasSig,JBool(false)), (Sig,JInt(80) ))
So, after that I used:
println("show values: " + data.values)
And had:
List(Map(Id -> 14706061, Rcvr -> 1, HasSig -> true, Sig -> 80), Map(Id -> 3425490, Rcvr -> 1, HasSig -> false, Sig -> 80))
But I don't know how to extract each Map from each position of the List.
I also tried to extract to a case class but I had 0 entries:
case class Example (Id: BigInt, Rcvr: Int, HasSig: Boolean, Sig: Int)
case class ExampleList (examples: List[Example])
implicit val formats = DefaultFormats.strict
val dataList = data.extract[ExampleList]
Thanks in advance for your help
PD. If I assign:
val dataList = data.values
The type of dataList (with getClass) is: class scala.collection.immutable.$colon$colon
PD2. SOLUTION
After the:
val data = json \\ "someKey"
I put:
val dataList = data.extract[JArray]
val examples = dataList.values
It returns an iterable Array with its Maps iterable, so fixed.
Checked with:
println("number of elements: " + examples.length)
and
println("show each item: " + examples.foreach(println))
Thanks for taking your time in reading.
If you want to extract into a Case Class instead of a Map, the correct type for extraction is List[Example], instead of ExampleList.
ExampleList has an attribute examples, your json doesn't. That is why you got an empty list.
import org.json4s.native.JsonMethods._
import org.json4s._
implicit val formats = DefaultFormats
val str = """[{"Id":14706061,
"Rcvr":1,
"HasSig":true,
"Sig":80},
{"Id":3425490,
"Rcvr":1,
"HasSig":false,
"Sig": 80}]"""
case class Example (Id: BigInt, Rcvr: Int, HasSig: Boolean, Sig: Int)
val json = parse(str)
val examples = json.extract[List[Example]]
Hope the below code helps.
enter code here
val iList = List(Map("Id" -> 14706061, "Rcvr" -> 1, "HasSig" -> "true", "Sig" -> 80),
Map("Id" -> 3425490, "Rcvr" -> 1, "HasSig" -> false, "Sig" -> 80))
for(i <-0 until iList.size){val lMap = iList(i)println("Id: " + lMap("Id"))}
The result of Json4s decoding frequently scramble the sequence of element in a JObject if decoding into a HashMap, so I tried to decode into ListMap instead. However, there seems to be no way of doing this, when I run the following simple program:
val v: ListMap[String, Int] = ListMap("a" -> 1, "b" -> 2)
val json = JsonMethods.compact(Extraction.decompose(v))
val v2 = Extraction.extract[ListMap[String, Int]](JsonMethods.parse(json))
assert(v == v2)
The following error message was thrown:
scala.collection.immutable.Map$Map2 cannot be cast to scala.collection.immutable.ListMap
java.lang.ClassCastException: scala.collection.immutable.Map$Map2 cannot be cast to scala.collection.immutable.ListMap
Is there an easy way to fix this? Or should I switch to more recent Json libraries (Argonaut/Circe) instead?
No, you can't do this. At least not this way. According to the JSON spec
An object is an unordered set of name/value pairs.
And all the standard libraries treat it that way. It means that the order is already scrambled when you/library do the initial parsing into intermediate data structure. Moreover, you can't even guarantee that the JSON will be {"a":1, "b":2} instead of {"b":2, "a":1}
The only way to preserve the order is to store in inside the JSON in a way that enforces the order and the only such thing is an ordered list of values aka array. So you can do something like this:
val v: ListMap[String, Int] = ListMap("c" -> 1, "AaAa" -> 2, "BBBB" -> 3, "AaBB" -> 4, "BBAa" -> 5)
val jsonBad = JsonMethods.compact(Extraction.decompose(v))
val bad = Extraction.extract[Map[String, Int]](JsonMethods.parse(jsonBad))
val jsonGood = JsonMethods.compact(Extraction.decompose(v.toList))
val good = ListMap(Extraction.extract[List[(String, Int)]](JsonMethods.parse(jsonGood)): _*)
println(s"'$jsonBad' => $bad")
println(s"'$jsonGood' => $good")
Which prints
'{"c":1,"AaAa":2,"BBBB":3,"AaBB":4,"BBAa":5}' => Map(AaAa -> 2, BBBB -> 3, AaBB -> 4, BBAa -> 5, c -> 1)
'[{"c":1},{"AaAa":2},{"BBBB":3},{"AaBB":4},{"BBAa":5}]' => ListMap(c -> 1, AaAa -> 2, BBBB -> 3, AaBB -> 4, BBAa -> 5)
Here is a library, which supports all Scala collections, so you can parse and serialize to/from ListMap easy, also it serializes case class fields in stable order of declaration:
libraryDependencies ++= Seq(
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-core" % "0.29.2" % Compile,
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-macros" % "0.29.2" % Provided // required only in compile-time
)
import com.github.plokhotnyuk.jsoniter_scala.macros._
import com.github.plokhotnyuk.jsoniter_scala.core._
val codec = JsonCodecMaker.make[ListMap[String, Int]](CodecMakerConfig())
val v: ListMap[String, Int] = ListMap("a" -> 1, "b" -> 2, "c" -> 3, "d" -> 4, "e" -> 5)
val json = writeToArray(codec, v)
val v2 = readFromArray(codec, json)
require(v == v2)
I have a Json object, which is an output of Json.parse, which is of format
{ "main_data" : [ {"a" : 1, "b" :2} , {"a" : 3, "b" : 4}, {"a" : 5, "b" : 6}]}.
I want to create a list out of this Json object with the values of all the "a" key. So in the above example it will be [1,3,5]. As I am new to functional programming the first thing came to my mind was to write a For loop and traverse through the Json object to get the list.
But I was wondering is there a Functional/Scala way to do the above, using Map or flatMap ?
import play.api.libs.json._
val parsed = Json.parse("""{"main_data":[{"a":1,"b":2},{"a":3,"b":4},{"a":5,"b":6}]}""")
val keys = parsed \\ "a"
val result = keys.flatMap(_.asOpt[Int]) // List(1, 3, 5)
You can use something like this
val json = Json.parse(yourJsonString)
val aKeys = json \\ "a"
// aKeys: Seq[play.api.libs.json.JsValue] = List(1, 3, 5)
// If you need integers instead of JsValues, just use map
val integersList = aKeys.map(x => x.as[Int])
There are plenty of choices. Let's say you have:
import play.api.libs.json._
val txt =
"""
|{ "main_data" : [ {"a" : 1, "b" :2} , {"a" : 3, "b" : 4}, {"a" : 5, "b" : 6}]}
""".stripMargin
val json = Json.parse(txt)
First approach if you are interested only in a:
(json \ "main_data")
.as[List[Map[String, Int]]]
.flatten
.foldLeft(List[Int]()){
case (acc, ("a", i)) => acc :+ i
case (acc, _) => acc
}
The second more general:
(json \ "main_data")
.as[List[Map[String, Int]]]
.flatten
.groupBy(_._1)
.map {
case (k, list) => k -> list.map(_._2)
}
.get("a")
And the result is:
res0: Option[List[Int]] = Some(List(1, 3, 5))
I was wondering if there is a parser or an easy way to iterate through a json object without knowing the keys/schema of the json ahead of time in scala. I took a look at a few libraries like json4s, but it seems to still require knowing the schema ahead of time before extracting the fields. I just want to iterate over each field, extract the fields and print out their values something like:
json.foreachkey(key -> println(key +":" + json.get(key))
In Play Json you'll initially parse your json into a JsValue; you can then pattern-match this to determine if it is a JsObject (note that you can find the fields of this using fields or value), a JsArray (again, note the value), or a primitive such as JsString or JsNull
def parse(jsVal: JsValue) {
jsVal match {
case json: JsObject =>
case json: JsArray =>
case json: JsString =>
...
}
}
If by json you mean any JValue, then json4s seems to have this functionality out of the box:
scala> import org.json4s.JsonDSL._
import org.json4s.JsonDSL._
scala> import org.json4s.native.JsonMethods._
import org.json4s.native.JsonMethods._
scala> val json = parse(""" { "numbers" : [1, 2, 3, 4] } """)
json: org.json4s.JValue = JObject(List((numbers,JArray(List(JInt(1), JInt(2), JInt(3), JInt(4))))))
scala> compact(render(json))
res1: String = {"numbers":[1,2,3,4]}
Use liftweb, it allows you to parse the json first into JValue - then extract native scala objects from it, no matter the schema:
val jsonString = """{"menu": {
| "id": "file",
| "value": "File",
| "popup": {
| "menuitem": [
| {"value": "New", "onclick": "CreateNewDoc()"},
| {"value": "Open", "onclick": "OpenDoc()"},
| {"value": "Close", "onclick": "CloseDoc()"}
| ]
| }
|}}""".stripMargin
val jVal: JValue = parse(jsonString)
jVal.values
>>> Map(menu -> Map(id -> file, value -> File, popup -> Map(menuitem -> List(Map(value -> New, onclick -> CreateNewDoc()), Map(value -> Open, onclick -> OpenDoc()), Map(value -> Close, onclick -> CloseDoc())))))