is it possible to decode JSON into ListMap using Json4s - json

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)

Related

How to extract List and Map in json4s

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"))}

Json#arr w/ List[Int] Input

Looking at this helpful answer on encoding a List[A] in the Play JSON library,
I tried to use Json#arr:
import play.api.libs.json._
scala> Json.arr( 1, 2, 3 )
res21: play.api.libs.json.JsArray = [1,2,3]
All works well so far, but what if I have a List[Int]:
scala> Json.arr( List(1,2,3) )
res22: play.api.libs.json.JsArray = [[1,2,3]] // not what I wanted
I attempted to pass the List[Int] as var-args (if that's the right term):
scala> Json.arr( List(1,2,3): _* )
<console>:18: error: type mismatch;
found : List[Int]
required: Seq[play.api.libs.json.Json.JsValueWrapper]
Json.arr( List(1,2,3): _* )
^
But that did not work.
Then, I tried to create a List[JsValueWrapper], and then pass that, via var-args, to Json#arr:
scala> List(1,2,3).map(Json.toJsFieldJsValueWrapper)
<console>:18: error: No Json serializer found for type T. Try to implement an implicit Writes or Format for this type.
List(1,2,3).map(Json.toJsFieldJsValueWrapper)
^
Although that failed, the following works when applied to a single Int:
scala> Json.toJsFieldJsValueWrapper(1)
res27: play.api.libs.json.Json.JsValueWrapper = JsValueWrapperImpl(1)
scala> Json.arr(res27)
res28: play.api.libs.json.JsArray = [1]
How can I pass a List[Int] into Json.arr to get an output of [1,2,3], i.e. a JsArray consisting of three JsNumber's?
You could use Json.toJson, validate as a JsArray, or return an empty one if invalid:
scala> val list = List(1, 2, 3)
list: List[Int] = List(1, 2, 3)
scala> Json.toJson(list).validate[JsArray].getOrElse(JsArray())
res6: play.api.libs.json.JsArray = [1,2,3]
You could also modify your lambda slightly with Json.arr:
scala> Json.arr(list.map(Json.toJsFieldJsValueWrapper(_)): _*)
res10: play.api.libs.json.JsArray = [1,2,3]
It seems as though without the parentheses, the compiler is having trouble inferring what T is. This is because the compiler tries to resolve the implicit before eta-expanding the anonymous function, so it doesn't know what argument will be passed to Json.toJsFieldJsValueWrapper yet. Json.toJsFieldJsValueWrapper(_) is fundamentally different because it expands to a slightly different Function1:
x => Json.toJsFieldJsValueWrapper(x)
Here the compiler knows that x will be an Int, so the implicit Writes[Int] is resolved.
How about just using Json.toJson and casting to a JsArray (if you need to)?
scala> Json.toJson(List(1,2,3)).as[JsArray]
res0: play.api.libs.json.JsArray = [1,2,3]

Converting a JSON object to a value list of a particular key in scala (play)

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

Update a JsObject by adding an item to one of its branches holding an array of numbers

I have JsObject of dynamic content, which could for example look like this:
{
"foo": "bar",
"viewedTaskIds": [1, 2, 3, 4]
}
viewedTaskIds is an array of integers.
What I would like to do, is update this JsObject and add a new number (let's say 5) to viewTaskIds. I want the result to be a JsObject.
How to do this in a Play Framework Scala application (v2.3)?
EDIT: In case the branch viewedTaskIds doesn't exist, it should be created with an array containing only the new number.
You can use a JSON transformer. In particular, you want to do an update on a branch.
val transformer = (__ \ "viewedTaskIds").json.update(
__.read[JsArray].map(_.append(JsNumber(5)))
)
This will look for the branch with the array you want to update. update accepts a Reads[A <: JsValue] to transform the value. We'll read it as a JsArray, and then map it to append JsNumber(5), if successful.
Then all we need to do is apply the transformer to a JsValue:
val js = Json.parse("""{
"foo": "bar",
"viewedTaskIds": [1, 2, 3, 4]
}""")
scala> js.transform(transformer)
res6: play.api.libs.json.JsResult[play.api.libs.json.JsObject] = JsSuccess({"foo":"bar","viewedTaskIds":[1,2,3,4,5]},/viewedTaskIds)
In the case where you want to supply an empty array where the branch has errors, you could add orElse to the transformer and supply Reads.pure(JsArray()) to be sure that it exists.
val transformer = (__ \ "viewedTaskIds").json.update(
__.read[JsArray].orElse(Reads.pure(JsArray())).map(_.append(JsNumber(5)))
)
Unfortunately, I'm having a hard time finding a nice solution to provide an empty array when the path doesn't exist at all. orElse doesn't work, because the path isn't there at the top-level of the transformer. What has worked is providing a fallback transformer. It's not very pretty, and it's crude in this form, but it works:
val fallback = __.json.update((__ \ "viewedTaskIds").json.put(JsArray(Seq(JsNumber(5)))))
scala> js.transform(transformer).orElse(js.transform(fallback))
res19: play.api.libs.json.JsResult[play.api.libs.json.JsObject] = JsSuccess({"foo":"bar","viewedTaskIds":[5]},)
If this is a repeated operation, you should create a transformer using update and call it using transform. You can read more about transformers here.
If this is a one-off operation, though, you might want to use the basic operations on JsObject and JsArray. Specifically, you can add and remove key-value pairs from JsObject with + and -, append elements to a JsArray with :+, and merge JsObjects with ++:
val model = Json.parse("""{ "foo": "bar", "viewedTaskIds": [1, 2, 3, 4] }""").as[JsObject]
val newIds = (model \ "viewedTaskIds").as[JsArray] :+ JsNumber(5)
val newModel = model - "viewedTaskIds" + ("viewedTaskIds" -> newIds)
//alternative to using - and +:
val newModel = model ++ Json.obj("viewedTaskIds" -> newIds)
//one liner:
val newModel = model ++ Json.obj("viewedTaskIds" -> ((model \ "viewedTaskIds").as[Seq[Int]] :+ 5))
As noted in the comments, some of these operations (such as as[Seq[Int]) will throw exceptions if the JSON isn't formatted as expected. Transformers are better suited to handle this kind of situation, and the techniques here are to be used cautiously.

Decoding JSON to a Map in Scala

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)