Reputation: 119
Scala Experts , need your help . task is to parse this json and return list of ModelIds for store 'livingstone' which has 'organic' = true.
In this case only RT001 has organic value as true.Please help.
Note: trying to use existing liftweb library .
package Exercises
import net.liftweb.json.{DefaultFormats, _}
object test1 extends App {
val json_response =
"""{
"requestId": "91ee60d5f1b45e#316",
"error": null,
"errorMessages": [
],
"entries": [
{
"modelId":"RT001",
"sku": "SKU-ASC001",
"store": "livingstone",
"ttlInSeconds": 8000,
"metadata": {
"manufactured_date": "2019-01-22T01:25Z",
"organic": "true"
}
},
{
"modelId":"RT002",
"sku": "SKU-ASC002",
"store": "livingstone",
"ttlInSeconds": 8000,
"metadata": {
"manufactured_date": "2019-10-03T01:25Z",
"organic": "false"
}
}
] }"""
val json = parse(json_response)
implicit val formats = DefaultFormats
val elements = (json \\ "entries").children
for (subs <- elements) {
val m = subs.extract[Subs]
println(s"Subscriptions: ${m.modelId}, ${m.store}")
println(" k,v: " + m.metadata.exists(_ == ("organic", "true")))
}
case class Subs(modelId: String, store: String, metadata: Map[String, String])
}
Getting Error. Also need help how to filter based on store=living stone and organic=true .
Exception in thread "main" net.liftweb.json.MappingException: No usable value for modelId
Do not know how to convert JArray(List(JString(RT001), JString(RT002))) into class java.lang.String
Final Working code with help from experts:
val json = parse(json_response)
implicit val formats = DefaultFormats
case class Sales(modelId: String, sku: String, store: String, ttlInSeconds: Int, metadata: Map[String, String])
case class Response(entries: List[Sales])
val json1 = parse(json_response)
val response = json.extract[Response]
val subs = response.entries.filter { e =>
e.store == "livingstone" &&
e.metadata.get("organic").contains("true")
}
subs.foreach(x=>println(x.modelId))
Upvotes: 0
Views: 1189
Reputation: 27356
When processing JSON it is best to convert the whole structure to Scala and then process the Scala, rather than directly processing the JSON.
So create a Response
class and extract that in a single operation, and then process the entries
field as appropriate.
Here is some completely untested code:
case class Response(entries: List[Subs])
val json = parse(json_response)
val response = json.extract[Response]
val subs = response.entries.filter{e =>
e.store == "livingstone" &&
e.metadata.get("organic").contains("true")
}
Note that this should work in any JSON library that allows you to extract a class
from JSON.
Upvotes: 2
Reputation: 4063
So, as it was suggested in comments section already, you can proceed with circe library instead of Lift framework, because it is much more modern and widely used solution.
What you need to do - declare structures, like case class
'es, which represents your json. It's not recommended approach, to operate over raw JSON - rule of thumb - parse it into some meaningful structure and then work with it.
Along with, structure declaration, you also Encoder
and Decoder
for let's say "non standard" cases - like Boolean
which is String
in cases of organic
field.
In your case the code might look like:
object App {
def main(args: Array[String]): Unit = {
import io.circe._, io.circe.generic.semiauto._, io.circe.generic.auto._, io.circe.parser._
/**
* General purpose response wrapper. Entries type might differ, as I can suppose, that's why it is generic.
* 'errorMessages' - since it is empty array in example, I can only guess about exact type. For sake of example
* let's say it is strings. And same for 'error' field.
*/
case class Response[E](requestId: String, error: Option[String], errorMessages: List[String], entries: List[E])
object Response {
implicit def decoder[E](implicit d: Decoder[E]): Decoder[Response[E]] = deriveDecoder[Response[E]]
implicit def encoder[E](implicit e: Encoder[E]): Encoder[Response[E]] = deriveEncoder[Response[E]]
}
case class Product(modelId: String, sku: String, store: String, ttlInSeconds: Int, metadata: ProductMetadata)
case class ProductMetadata(manufactured_date: ZonedDateTime, organic: Boolean)
object ProductMetadata {
// Boolean codec required - because `organic` is a string in JSON, which has boolean type
implicit val booleanDecoder: Decoder[Boolean] = Decoder[String].emapTry(value => Try(value.toBoolean))
implicit val booleanEncoder: Encoder[Boolean] = Encoder[String].contramap(_.toString)
implicit val decoder: Decoder[ProductMetadata] = deriveDecoder[ProductMetadata]
implicit def encoder: Encoder[ProductMetadata] = deriveEncoder[ProductMetadata]
}
val json =
s"""
|{
| "requestId":"91ee60d5f1b45e#316",
| "error":null,
| "errorMessages":[
|
| ],
| "entries":[
| {
| "modelId":"RT001",
| "sku":"SKU-ASC001",
| "store":"livingstone",
| "ttlInSeconds":8000,
| "metadata":{
| "manufactured_date":"2019-01-22T01:25Z",
| "organic":"true"
| }
| },
| {
| "modelId":"RT002",
| "sku":"SKU-ASC002",
| "store":"livingstone",
| "ttlInSeconds":8000,
| "metadata":{
| "manufactured_date":"2019-10-03T01:25Z",
| "organic":"false"
| }
| }
| ]
|}
|""".stripMargin
val parseResult: Either[Error, List[String]] =
for {
parsedJson <- parse(json)
response <- parsedJson.as[Response[Product]]
} yield {
response.entries.collect {
case Product(modelId, _, "livingstone", _, ProductMetadata(_, true)) => modelId
}
}
println(parseResult)
}
which will produce next result
Right(List(RT001))
Hope this helps!
Upvotes: 1