How to parse JSON in Scala using standard Scala classes? - json

I am using the build in JSON class in Scala 2.8 to parse JSON code. I don't want to use the Liftweb one or any other due to minimizing dependencies.
The way I am doing it seems too imperative, is there a better way to do it?
import scala.util.parsing.json._
...
val json:Option[Any] = JSON.parseFull(jsonString)
val map:Map[String,Any] = json.get.asInstanceOf[Map[String, Any]]
val languages:List[Any] = map.get("languages").get.asInstanceOf[List[Any]]
languages.foreach( langMap => {
val language:Map[String,Any] = langMap.asInstanceOf[Map[String,Any]]
val name:String = language.get("name").get.asInstanceOf[String]
val isActive:Boolean = language.get("is_active").get.asInstanceOf[Boolean]
val completeness:Double = language.get("completeness").get.asInstanceOf[Double]
}

This is a solution based on extractors which will do the class cast:
class CC[T] { def unapply(a:Any):Option[T] = Some(a.asInstanceOf[T]) }
object M extends CC[Map[String, Any]]
object L extends CC[List[Any]]
object S extends CC[String]
object D extends CC[Double]
object B extends CC[Boolean]
val jsonString =
"""
{
"languages": [{
"name": "English",
"is_active": true,
"completeness": 2.5
}, {
"name": "Latin",
"is_active": false,
"completeness": 0.9
}]
}
""".stripMargin
val result = for {
Some(M(map)) <- List(JSON.parseFull(jsonString))
L(languages) = map("languages")
M(language) <- languages
S(name) = language("name")
B(active) = language("is_active")
D(completeness) = language("completeness")
} yield {
(name, active, completeness)
}
assert( result == List(("English",true,2.5), ("Latin",false,0.9)))
At the start of the for loop I artificially wrap the result in a list so that it yields a list at the end. Then in the rest of the for loop I use the fact that generators (using <-) and value definitions (using =) will make use of the unapply methods.
(Older answer edited away - check edit history if you're curious)

This is the way I do the pattern match:
val result = JSON.parseFull(jsonStr)
result match {
// Matches if jsonStr is valid JSON and represents a Map of Strings to Any
case Some(map: Map[String, Any]) => println(map)
case None => println("Parsing failed")
case other => println("Unknown data structure: " + other)
}

I like #huynhjl's answer, it led me down the right path. However, it isn't great at handling error conditions. If the desired node does not exist, you get a cast exception. I've adapted this slightly to make use of Option to better handle this.
class CC[T] {
def unapply(a:Option[Any]):Option[T] = if (a.isEmpty) {
None
} else {
Some(a.get.asInstanceOf[T])
}
}
object M extends CC[Map[String, Any]]
object L extends CC[List[Any]]
object S extends CC[String]
object D extends CC[Double]
object B extends CC[Boolean]
for {
M(map) <- List(JSON.parseFull(jsonString))
L(languages) = map.get("languages")
language <- languages
M(lang) = Some(language)
S(name) = lang.get("name")
B(active) = lang.get("is_active")
D(completeness) = lang.get("completeness")
} yield {
(name, active, completeness)
}
Of course, this doesn't handle errors so much as avoid them. This will yield an empty list if any of the json nodes are missing. You can use a match to check for the presence of a node before acting...
for {
M(map) <- Some(JSON.parseFull(jsonString))
} yield {
map.get("languages") match {
case L(languages) => {
for {
language <- languages
M(lang) = Some(language)
S(name) = lang.get("name")
B(active) = lang.get("is_active")
D(completeness) = lang.get("completeness")
} yield {
(name, active, completeness)
}
}
case None => "bad json"
}
}

I tried a few things, favouring pattern matching as a way of avoiding casting but ran into trouble with type erasure on the collection types.
The main problem seems to be that the complete type of the parse result mirrors the structure of the JSON data and is either cumbersome or impossible to fully state. I guess that is why Any is used to truncate the type definitions. Using Any leads to the need for casting.
I've hacked something below which is concise but is extremely specific to the JSON data implied by the code in the question. Something more general would be more satisfactory but I'm not sure if it would be very elegant.
implicit def any2string(a: Any) = a.toString
implicit def any2boolean(a: Any) = a.asInstanceOf[Boolean]
implicit def any2double(a: Any) = a.asInstanceOf[Double]
case class Language(name: String, isActive: Boolean, completeness: Double)
val languages = JSON.parseFull(jstr) match {
case Some(x) => {
val m = x.asInstanceOf[Map[String, List[Map[String, Any]]]]
m("languages") map {l => Language(l("name"), l("isActive"), l("completeness"))}
}
case None => Nil
}
languages foreach {println}

val jsonString =
"""
|{
| "languages": [{
| "name": "English",
| "is_active": true,
| "completeness": 2.5
| }, {
| "name": "Latin",
| "is_active": false,
| "completeness": 0.9
| }]
|}
""".stripMargin
val result = JSON.parseFull(jsonString).map {
case json: Map[String, List[Map[String, Any]]] =>
json("languages").map(l => (l("name"), l("is_active"), l("completeness")))
}.get
println(result)
assert( result == List(("English", true, 2.5), ("Latin", false, 0.9)) )

You can do like this! Very easy to parse JSON code :P
package org.sqkb.service.common.bean
import java.text.SimpleDateFormat
import org.json4s
import org.json4s.JValue
import org.json4s.jackson.JsonMethods._
//import org.sqkb.service.common.kit.{IsvCode}
import scala.util.Try
/**
*
*/
case class Order(log: String) {
implicit lazy val formats = org.json4s.DefaultFormats
lazy val json: json4s.JValue = parse(log)
lazy val create_time: String = (json \ "create_time").extractOrElse("1970-01-01 00:00:00")
lazy val site_id: String = (json \ "site_id").extractOrElse("")
lazy val alipay_total_price: Double = (json \ "alipay_total_price").extractOpt[String].filter(_.nonEmpty).getOrElse("0").toDouble
lazy val gmv: Double = alipay_total_price
lazy val pub_share_pre_fee: Double = (json \ "pub_share_pre_fee").extractOpt[String].filter(_.nonEmpty).getOrElse("0").toDouble
lazy val profit: Double = pub_share_pre_fee
lazy val trade_id: String = (json \ "trade_id").extractOrElse("")
lazy val unid: Long = Try((json \ "unid").extractOpt[String].filter(_.nonEmpty).get.toLong).getOrElse(0L)
lazy val cate_id1: Int = (json \ "cate_id").extractOrElse(0)
lazy val cate_id2: Int = (json \ "subcate_id").extractOrElse(0)
lazy val cate_id3: Int = (json \ "cate_id3").extractOrElse(0)
lazy val cate_id4: Int = (json \ "cate_id4").extractOrElse(0)
lazy val coupon_id: Long = (json \ "coupon_id").extractOrElse(0)
lazy val platform: Option[String] = Order.siteMap.get(site_id)
def time_fmt(fmt: String = "yyyy-MM-dd HH:mm:ss"): String = {
val dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
val date = dateFormat.parse(this.create_time)
new SimpleDateFormat(fmt).format(date)
}
}

This is the way I do the Scala Parser Combinator Library:
import scala.util.parsing.combinator._
class ImprovedJsonParser extends JavaTokenParsers {
def obj: Parser[Map[String, Any]] =
"{" ~> repsep(member, ",") <~ "}" ^^ (Map() ++ _)
def array: Parser[List[Any]] =
"[" ~> repsep(value, ",") <~ "]"
def member: Parser[(String, Any)] =
stringLiteral ~ ":" ~ value ^^ { case name ~ ":" ~ value => (name, value) }
def value: Parser[Any] = (
obj
| array
| stringLiteral
| floatingPointNumber ^^ (_.toDouble)
|"true"
|"false"
)
}
object ImprovedJsonParserTest extends ImprovedJsonParser {
def main(args: Array[String]) {
val jsonString =
"""
{
"languages": [{
"name": "English",
"is_active": true,
"completeness": 2.5
}, {
"name": "Latin",
"is_active": false,
"completeness": 0.9
}]
}
""".stripMargin
val result = parseAll(value, jsonString)
println(result)
}
}

scala.util.parsing.json.JSON is deprecated.
Here is another approach with circe. FYI documentation: https://circe.github.io/circe/cursors.html
Add the dependency in build.sbt, I used scala 2.13.4, note the scala version must align with the library version.
val circeVersion = "0.14.0-M2"
libraryDependencies ++= Seq(
"io.circe" %% "circe-core" % circeVersion,
"io.circe" %% "circe-generic" % circeVersion,
"io.circe" %% "circe-parser" % circeVersion
)
Example 1:
case class Person(name: String, age: Int)
object Main {
def main(args: Array[String]): Unit = {
val input =
"""
|{
| "kind": "Listing",
| "data": [
| {
| "name": "Frodo",
| "age": 51
| },
| {
| "name": "Bilbo",
| "age": 60
| }
| ]
|}
|""".stripMargin
implicit val decoderPerson: Decoder[Person] = deriveDecoder[Person] // decoder required to parse to custom object
val parseResult: Json = circe.parser.parse(input).getOrElse(Json.Null)
val data: ACursor = parseResult.hcursor.downField("data") // get the data field
val personList: List[Person] = data.as[List[Person]].getOrElse(null) // parse the dataField to a list of Person
for {
person <- personList
} println(person.name + " is " + person.age)
}
}
Example 2, json has an object within an object:
case class Person(name: String, age: Int, position: Position)
case class Position(x: Int, y: Int)
object Main {
def main(args: Array[String]): Unit = {
val input =
"""
|{
| "kind": "Listing",
| "data": [
| {
| "name": "Frodo",
| "age": 51,
| "position": {
| "x": 10,
| "y": 20
| }
| },
| {
| "name": "Bilbo",
| "age": 60,
| "position": {
| "x": 75,
| "y": 85
| }
| }
| ]
|}
|""".stripMargin
implicit val decoderPosition: Decoder[Position] = deriveDecoder[Position] // must be defined before the Person decoder
implicit val decoderPerson: Decoder[Person] = deriveDecoder[Person]
val parseResult = circe.parser.parse(input).getOrElse(Json.Null)
val data = parseResult.hcursor.downField("data")
val personList = data.as[List[Person]].getOrElse(null)
for {
person <- personList
} println(person.name + " is " + person.age + " at " + person.position)
}
}

Related

Scala parses non-canonical JSON

The following data can be seen with different value types. How can I get the desired output?
package ceshi
import scala.util.parsing.json.JSON
object ceshi1212 {
def main(args: Array[String]): Unit = {
class CC[T] {
def unapply(a: Any): Option[T] = Some(a.asInstanceOf[T])
}
object M extends CC[Map[String, Any]]
object L extends CC[List[Any]]
object S extends CC[String]
val jsonString =
"""
{
"languages": [
{
"name": "English",
"is_active": "true",
"completeness": "asdf"
},
{
"name": "Latin",
"is_active": "asdf",
"completeness": "232"
}
,{
"name": "Latin",
"is_active": "0009"
}
,
"error"
]
}
""".stripMargin
// 不规则json error和并列的数组类型不同 怎么解析自动跳过?
val result = for {
Some(M(map)) <- List(JSON.parseFull(jsonString))
L(languages) = map("languages")
M(language) <- languages
S(name) = language("name")
S(active) = language("is_active")
S(completeness) = language.getOrElse("completeness","--")
} yield {
(name, active, completeness)
}
println(result)
//i want result is: List((English,true,asdf), (Latin,asdf,232),(Latain,0009,""))
}
}
i want get result is List((English,true,asdf), (Latin,asdf,232),(Latain,0009,""))
note: 1 The string is not always at the end of the array, and the position is indeterminate
2 The three keys I need may not be complete
As said in the comments there are other libraries to be recommended for working with json have a look at this post to get an overview: What JSON library to use in Scala?
Answer to your question with specific framework (play-json)
Personally I can recommend to use the play json framework.
To obtain the result you have described with play json, your code might look like this:
import play.api.libs.json._
val json: JsValue = Json.parse(jsonString)
val list = (json \ "languages").as[Seq[JsValue]]
val names = list.map(x => ((x\"name").validate[String] match {
case JsSuccess(v, p ) => v
case _ => ""
}
))
val isActives = list.map(x => ((x\"is_active").validate[String] match {
case JsSuccess(v, p ) => v
case _ => ""
}
))
val completeness = list.map(x => ((x\"completeness").validate[String] match {
case JsSuccess(v, p ) => v
case _ => ""
}
))
// need to know in advance what is your max length of your tuple (tmax)
// since 3rd value "completeness" can be missing, so we just take "" instead
val tmax = 3
val res = for(idx <-0 to tmax-1) yield (names(idx),isActives(idx),completeness(idx))
res.toList
// List[(String, String, String)] = List((English,true,asdf), (Latin,asdf,232), (Latin,0009,""))
There's also a very good documentation for the play json framework, just check it out yourself: https://www.playframework.com/documentation/2.8.x/ScalaJson
If you can switch parser library to circe, you can deal with this types of bad data.
Given you have data model
import io.circe.generic.semiauto._
import io.circe.parser.decode
import io.circe.{Decoder, Json}
case class Languages(languages: Seq[Language])
case class Language(name: String, is_active: String, completeness: Option[String])
You can define a fault-tolerant seq decoder that would skip bad data rather than crash whole parse
def tolerantSeqDecoder[A: Decoder]: Decoder[Seq[A]] = Decoder.decodeSeq(Decoder[A]
.either(Decoder[Json])).map(_.flatMap(_.left.toOption))
and the rest...
val jsonString = """
{
"languages": [
{
"name": "English",
"is_active": "true",
"completeness": "asdf"
},
{
"name": "Latin",
"is_active": "asdf",
"completeness": "232"
},
{
"name": "Latin",
"is_active": "0009"
},
"error"
]
}
"""
val languageDecoder = deriveDecoder[Language]
implicit val tolerantDecoder = tolerantSeqDecoder[Language](languageDecoder)
implicit val languagesDecoder = deriveDecoder[Languages]
val parsed = decode[Languages](jsonString)
println(parsed)
out:
Right(Languages(List(Language(English,true,Some(asdf)), Language(Latin,asdf,Some(232)), Language(Latin,0009,None))))
This approach was suggested by one of circe developers: How do I ignore decoding failures in a JSON array?

Dynamic type casting for JsValue field in Scala Play

Since I'm writing a function to request data from another API in my Scala code, the response Json has the format like this:
"data": {
"attributeName": "some String",
"attributeValue": false,
"attributeSource": "Manual",
"attributeValueLabel": null
},
"data": {
"attributeName": "some String",
"attributeValue": "daily",
"attributeSource": "Manual",
"attributeValueLabel": "Almost Daily"
}
Note that sometimes the type of attributeValue is String value, some other time it's a Boolean value.
So I'm trying to write my own Reads and Writes to read the type dynamically.
case class Data(attributeName: Option[String], attributeValue: Option[String], attributeSource: Option[String], attributeValueLabel: Option[String])
object Data{
implicit val readsData: Reads[Data] = {
new Reads[Data] {
def reads(json: JsValue) = {
val attrValue = (json \ "attributeValue").as[] // How to cast to Boolean some time, but some other time is a String here
......
}
}
}
So as you can see in my comment, I'm stuck at the part to cast the (json \ "attributeValue") to String/Boolean, base on the return type of the API. How can I do this?
You can try to parse it as String first and then as Boolean:
val strO = (json \ "attributeValue").asOpt[String]
val value: Option[String] = strO match {
case str#Some(_) => str
case None => (json \ "attributeValue").asOpt[Boolean].map(_.toString)
}
You can use the .orElse function when you are trying to read an attribute in different ways:
import play.api.libs.json.{JsPath, Json, Reads}
import play.api.libs.functional.syntax._
val json1 =
"""
|{
| "attributeName": "some String",
| "attributeValue": false
|}
""".stripMargin
val json2 =
"""
|{
| "attributeName": "some String",
| "attributeValue": "daily"
|}
""".stripMargin
// I modified you case class to make the example short
case class Data(attributeName: String, attributeValue: String)
object Data {
// No need to define a reads function, just assign the value
implicit val readsData: Reads[Data] = (
(JsPath \ "attributeName").read[String] and
// Try to read String, then fallback to Boolean (which maps into String)
(JsPath \ "attributeValue").read[String].orElse((JsPath \ "attributeValue").read[Boolean].map(_.toString))
)(Data.apply _)
}
println(Json.parse(json1).as[Data])
println(Json.parse(json2).as[Data])
Output:
Data(some String,false)
Data(some String,daily)

ScalaJsonCombinators how to read JSON to Map[String, CaseClass]

The JSON I need to parse is like
{
"state": "active",
"id": "11775",
"translations": {
"de_CH": {
"name": "Spiegel",
"url": "spiegel-sale"
},
"fr_CH": {
"name": "Miroirs",
"url": "promo-miroirs-femme"
}
}
In the translations, the keys de_CH and fr_CH are not known in advance. The other keys are known.
For me the translations object can be as modelled as a dictionary.
Here are the case class
case class Category(
id: String,
order: Int,
translations: Map[String, NodeTranslation]
)
case class NodeTranslation(name: String, url: String)
The ScalaJsonCombinators reads are
implicit val categoryReads = Json.format[Category
implicit val nodeTranslationReads = Json.format[NodeTranslation]]
How to read Map[String,NodeTranslation] in JSON ?
I didn't find anything for a Map there : https://www.playframework.com/documentation/2.6.x/ScalaJsonCombinators#complex-reads
Well that's how:
import play.api.libs.json._
import play.api.libs.functional.syntax._
case class Category(
id: String,
order: Int,
translations: Map[String, NodeTranslation]
)
case class NodeTranslation(name: String, url: String)
implicit val nodeTranslationReads = Json.format[NodeTranslation]
implicit val categoryReads: Reads[Category] = (
(__ \ 'id).read[String] and
Reads.pure[Int](123) and // your example json doesn't contain an order member so I'm not sure what you expect here
(__ \ 'translations).read[JsObject].map { obj =>
obj.value.mapValues(_.as[NodeTranslation]).toMap
}
)(Category.apply(_, _, _))
And now testing that in the repl:
val js =
"""
|{
|"state": "active",
|"id": "11775",
|"translations": {
| "de_CH": {
| "name": "Spiegel",
| "url": "spiegel-sale"
| },
| "fr_CH": {
| "name": "Miroirs",
| "url": "promo-miroirs-femme"
| }
| }
|}
""".stripMargin
val json = Json.parse(js)
json.as[Category]
the result is:
Category(11775,123,Map(de_CH -> NodeTranslation(Spiegel,spiegel-sale), fr_CH -> NodeTranslation(Miroirs,promo-miroirs-femme)))
Note that if you want to create a formatter for Category, you have to use:
(__ \ 'translations).format[JsObject].inmap(...)
nb: I really love play-json it's not always easy to use but I've yet to find a case where I can't get it to do what I need.

How to Parse Json String received from HTTP and loop through the values

I'm using Scala and Swagger and i need help figuring out how to loop through the values in the json and use those values for checking and others.
The json string that is returned after HTTP get request looks like this:
{
"count": 3,
"items": [
{
"Id": "fd0a9e5a",
"DbName": "Xterior Prod",
"Name": "XP"
},
{
"Id": "4158a1a6",
"DbName": "Invidibi Pappear",
"Name": "ISP"
},
{
"Id": "7e0c57046d3f",
"DbName": "Multi Test",
"Name": "MMP"
}]
}
My UI allows the user to input an ID. What i have to do is to loop through the Json value returned from the API and find the one that matches the ID entered. Once i find a match, i have to check if the database has "Test" keyword in it. If it does, i will need to show the DbName and the shortname.
I have found some guide here (e.g. Foreach with JSON Arrays in Play2 and Scala) but it did not work for me. When i run my code, i get this error:
play.api.libs.json.JsResultException: JsResultException(errors:List(((0)/Id,List(ValidationError(List(error.path.missing),WrappedArray()))), ((0)/DbName,List(ValidationError(List(error.path.missing),WrappedArray()))), ((1)/Id,List(ValidationError(List(error.path.missing),WrappedArray()))), ((1)/DbName,List(ValidationError(List(error.path.missing),WrappedArray()))), ((2)/Id,List(ValidationError(List(error.path.missing),WrappedArray()))), ((2)/DbName,List(ValidationError(List(error.path.missing),WrappedArray()))),
Here is my code:
case class DBInfo(Id: String, DbName: String, Name: String)
contentType = "application/json"
//get json from http
val httpClient = HttpClients.createDefault()
val httpResponse = httpClient.execute(new HttpGet("http://www.customers.com/dbInfo"))
val entity = httpResponse.getEntity
val content = fromInputStream(httpResponse.getEntity.getContent()).getLines().mkString
implicit val dbReader = Json.reads[DBInfo]
val dbList = (Json.parse(content) \ "items").as[List[DBInfo]]
dbList.foreach { dbI =>
if (dbI.Id == id)
if (dbI.DbName.contains("Test"))
println(dbI.DbName + " - " + dbI.Name)
else BadRequest("Not allowed")
else
BadRequest("ID not found")
}
id is the variable that holds the inputed ID by the user. Can someone tell me why the error and how to fix it? Thanks.
note: Please using import org.json4s.JsonAST or import play.api.libs.json
already got the answer. so this is how i did it:
case class databaseInfo(Id: String, DbName: String, Name: String)
class dbInfo{
def CheckDb(id: String): Option[String] = {
val httpClient = HttpClients.createDefault()
val httpResponse = httpClient.execute(new HttpGet("http://example.com"))
val content = fromInputStream(httpResponse.getEntity.getContent()).getLines().mkString
val envItems = (parse(content) \\ "items").children
for (items <- envItems) {
val dbItems = items.extract[databaseInfo]
if (dbItems.EnvId == Some(id)) {
if (equalsIgnoreCase(dbItems.DbName.mkString, "Test")) //do something
else //do something
}
}
None
}
}
Here is an approach using circe. You can navigate the JSON with a Cursor, and decode to a list of Environment using the Decoder[A] typeclass. Note that you work with Either[Failure, A] values.
import io.circe._
case class Environment(id: String, dbName: String, name: String)
implicit val environmentDecoder: Decoder[Environment] = Decoder.instance[Environment] {
json =>
for {
id <- json.downField("Id").as[String]
dbName <- json.downField("DbName").as[String]
name <- json.downField("Name").as[String]
} yield {
Environment(id, dbName, name)
}
}
// alternatively:
// implicit val environmentDecoder: Decoder[Environment] =
// Decoder.forProduct3[String, String, String, Environment]("Id", "DbName", "Name")(Environment.apply)
val text =
"""{
| "count": 3,
| "items": [{
| "Id": "fd0a9e5a",
| "DbName": "Xterior Prod",
| "Name": "XP"
| }, {
| "Id": "4158a1a6",
| "DbName": "Invidibi Pappear",
| "Name": "ISP"
| }, {
| "Id": "7e0c57046d3f",
| "DbName": "Multi Match Test",
| "Name": "MMP"
| }]
|}
""".stripMargin
val json = parser.parse(text).fold(_ => ???, json => json)
val res: Either[DecodingFailure, List[Environment]] = json.hcursor.downField("items").as[List[Environment]]
println(res)
// Right(List(Environment(fd0a9e5a,Xterior Prod,XP), Environment(4158a1a6,Invidibi Pappear,ISP), Environment(7e0c57046d3f,Multi Match Test,MMP)))
// or simply
// val res2 = parser.parse(text).right
// .flatMap(_.hcursor.downField("items").as[List[Environment]])
You can also use http4s' http4s-blaze-client and http4s-circe to do HTTP requests:
import org.http4s._
import org.http4s.circe._
import scalaz.concurrent._
val client = org.http4s.client.blaze.defaultClient
val fetchEnvironments: Task[List[Environment]] =
client.fetchAs[Json](Request(Method.GET, Uri.uri("http://example.com")))
.flatMap { json =>
json.hcursor.downField("items").as[List[Environment]].fold(
failure => Task.fail(failure),
xs => Task.now(xs)
)
}
val xs = fetchEnvironments.unsafePerformSync

In Scala, how to write an efficient json formatter for Map[IndexedSeq[String], Int]?

I think there is not a default format for Map[IndexedSeq[String], Int] in scala (right?) So I've written my own format as follows, however it's very slow. Is there a better way to do this?
class IndexedSeqToIntMapFormat() extends Format[Map[IndexedSeq[String], Int]] {
def writes(o: Map[IndexedSeq[String], Int]): JsValue = {
val mapItems: Seq[String] = o.toSeq.map{case (rowKey, index) => (index.toString +: rowKey).mkString(",")}
Json.obj("items" -> Json.toJson(mapItems))
}
def reads(json: JsValue): JsResult[Map[IndexedSeq[String], Int]] = {
val mapItemsAsString: IndexedSeq[String] = (json \ "items").as[IndexedSeq[String]]
val map: Map[IndexedSeq[String], Int] = mapItemsAsString.map(itemAsString => {
val item: IndexedSeq[String] = itemAsString.split(",").toIndexedSeq
val rowKey: IndexedSeq[String] = item.tail
val rowIndex: Int = item.head.toInt
(rowKey, rowIndex)
}).toMap
JsSuccess(map)
}
}
Thanks!
Can't say for sure whether the following approach is significantly faster than yours, but to my understanding, it does much more conform to the "spirit" of JSON. In a JSON serialization, each object and all its sub-objects and attributes should be named, by what they are. A list of custom string serializations of complex objects is not an actual JSON representation in my opinion.
Doing it in a "proper" JSON way should at least save some time in the parsing, as it does not require additional parsing work on strings but already provides all data in the needed places. And the code looks much more readable :-)
The resulting JSON should look like this:
"items": [
{ "keySeq": [ "key1", "key2", "key3" ], "value": 42 },
{ "keySeq": [ "key4", "key5" ], "value": 123 },
{ "keySeq": [ "key6", "key7", "key7" ], "value": 650 }
]
The formatter could be something like this:
class IndexedSeqToIntMapFormat() extends Format[Map[IndexedSeq[String], Int]] {
def writes(m: Map[IndexedSeq[String], Int]): JsValue = {
val objs = m.toSeq.map { case (keySeq, value) =>
Json.obj("keySeq" -> Json.toJson(keySeq), "value" -> JsNumber(value))
}
Json.obj("items" -> JsArray(objs))
}
def reads(json: JsValue): JsResult[Map[IndexedSeq[String], Int]] = {
val seq = (json \ "items").as[Seq[JsValue]] map { obj =>
( (obj \ "keySeq").as[IndexedSeq[String]], (obj \ "value").as[Int] )
}
JsSuccess(seq.toMap)
}
}
By the way, just out of curiosity - can you tell me in what context you need a such map?