简体   繁体   中英

Transform a List[(T, Double)] on a Json value with the play framework

I'm writing a Play 2.3.2 application, using Scala. I use ReactiveMongo to access a my mongoDB database.

I've a collection named recommendation.advices that store all the suggestion to the user.

A document has the following form:

{
    "_id" : ObjectId("54475f434be669af141677b7"),
    "id" : "b8e84cb9-4b3a-4c6c-b01d-af276747e4ad",
    "user" : {
        "id" : "",
        "email" : "luigi@gmail.com"
    },
    "output" : [
        {
            "tag" : "Vegetable:Carrots - alberto@gmail.com",
            "score" : 0
        },
        {
            "tag" : "Paper Goods:Liners - Baking Cups",
            "score" : 0
        },
        {
            "tag" : "Vegetable:Carrots - Jumbo",
            "score" : 0
        },
        {
            "tag" : "Paper Goods:Lialberto- Baking Cups",
            "score" : 0
        }
    ],
    "date" : 1413963587328,
    "type" : "system",
    "clicked" : true
}

Now I'm writing a method in my controller that returns all the data where "clicked": true , and I want to return a Json in the following form:

{"idR": "b8e84cb9-4b3a-4c6c-b01d-af276747e4ad",
"user" : {
        "id" : "",
        "email" : "luigi@gmail.com"
    },
    "tags" : [
        {
            "tag" : "Vegetable:Carrots - alberto@gmail.com",
            "score" : 0
        },
        {
            "tag" : "Paper Goods:Liners - Baking Cups",
            "score" : 0
        },
        {
            "tag" : "Vegetable:Carrots - Jumbo",
            "score" : 0
        },
        {
            "tag" : "Paper Goods:Lialberto- Baking Cups",
            "score" : 0
        }
    ]
}

How can I make that in play??

I try to implement the method as the following:

def clickedAdvises = Action.async {
       val query = Json.obj("clicked" -> true)
       Advices.find(query).toList flatMap { advices => 
         val results = for(el <- advices) yield Json.obj("idR" -> el.id, "user" -> el.user, "tags" -> el.output)
         Future{Ok(Json.obj("results" -> results))}

       }
     } 

The el.output is a List[(Tag, Double)], and for the Tag I've defined a formatter.

But the compiler gives me the following errors:

[error] /Users/alberto/git/bdrim/modules/recommendation-system/app/recommendationsystem/controllers/StatisticsController.scala:111: type mismatch;
[error]  found   : List[(recommendationsystem.models.Tag, Double)]
[error]  required: play.api.libs.json.Json.JsValueWrapper
[error]              val results = for(el <- advices) yield Json.obj("idR" -> el.id, "user" -> el.user, "tags" -> el.output)
[error]                                                                                                              ^
[error] /Users/alberto/git/bdrim/modules/recommendation-system/app/recommendationsystem/controllers/StatisticsController.scala:111: type mismatch;
[error]  found   : List[(recommendationsystem.models.Tag, Double)]
[error]  required: play.api.libs.json.Json.JsValueWrapper
[error]              val results = for(el <- advices) yield Json.obj("idR" -> el.id, "user" -> el.user, "tags" -> el.output)
[error]                                                                                                              ^
[error] one error found

How can I fixed that??

The error is self explanatory:

You need JsValue , instead you are giving a List[(recommendationsystem.models.Tag, Double)] which apparently is the type of output field in your Advice class.

The fact that you have a formatter for your Tag class does not change the situation, first because what you have is a List of tuples, not a single tag, second because formatter only works when you use toJson and fromJson macros.

So what you need is somehow convert this List of tuples to a JsValue .

JsArray is corresponding to JSON list. If you look at the code of it

case class JsArray(value: Seq[JsValue] = List()) extends JsValue 

you see that you can pass a Seq of JsValue s to it. So you need to somehow convert a tuple of (recommendationsystem.models.Tag, Double) to a JsObject (which is corresponding to a JSON object). You can do this simply with the following code:

JsObject(Seq(
  ("tag", tag),
  ("score", score)
))

or by using the Json.obj which is a helper factory mehtod for constructing a JsObject .

So the code will be:

val results = for { 
                el <- advices
              } yield 
                Json.obj(
                  "idR" -> el.id, 
                  "user" -> el.user, 
                  "tags" -> Json.arr(el.output.map(it => Json.obj((it._1, it._2)).asInstanceOf[JsValueWrapper]): _*)
                        )

Easier way!

If you refer to Play Doc about JSON you will see that you can convert objects to/from json by using play.api.libs.json.Json.toJson method. In order to do that you need an implicit writer for your classes which is pretty straightforward.

There are some macro helpers, for example Json.writes macro helps you create a writer for your class. This machinery is recursive so if you have implicit writers for all classes needed, then you can simply call Json.toJson(advice) and get the result.

So I would suggest to start writing an implicit writer for your advice class:

import play.api.libs.json._

implicit def adviceWriter = Json.writes[Advice]

then try

Json.toJson(advice)

Then compile the code, you will get compile errors because you need writer for all needed classes. Continue by providing a writer for them till you get no compile error!

This makes the code cleaner and more concise and focused, as the actual conversion to Json will not pollute the actual code. In addition, you can reuse the code without explicitly doing anything. You need just to bring implicit writers and readers to your context by importing them and using Json.toJson and Json.fromJson methods.

我认为您应该先将el.output转换为JsObject,然后再将其插入jsObject

Are you sure that el object can be called by something like el.id ? It's not JavaScript, even it's JsValue, it should called by (el \\ "id") .

I'm no pretty sure, but try using el(id) to call it and insert as a value of your JsObject like

Json.obj("idR" -> el(id))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM