I am using built-in jerson with playframework 2, and all I want is to serialize map, containing values of different type:
object AppWriter extends Writes[Application] {
def writes(app: Application): JsValue = {
Json.toJson(Map(
"id" -> app.getId.toString,
"name" -> app.getName,
"users" -> Seq(1, 2, 3)
))
}
}
In this case I have:
No Json deserializer found for type scala.collection.immutable.Map[java.lang.String,java.lang.Object].
Try to implement an implicit Writes or Format for this type
Navigatin through framework code shows that there is serializer for Map[String,V] types implicit def mapWrites[V] .. , but I cannot understand why it doesn't applied.
Can anybody help me?
UPD: I found simple workaround:
object AppWriter extends Writes[Application] {
def writes(app: Application): JsValue = {
Json.toJson(Map[String, JsValue](
"id" -> JsNumber(BigDecimal(app.getId)),
"name" -> JsString(app.getName),
"users" -> JsArray(Seq(1, 2, 3).map(x => JsNumber(x)))
))
}
}
but this is not so elegant...
The standard way to do this is by creating a JsObject from the individual key-value pairs for the fields—not by putting the pairs into a map. For example, assuming your Application looks like this:
case class Application(getId: Int, getName: String)
You could write:
import play.api.libs.json._, Json.toJson
implicit object AppWriter extends Writes[Application] {
def writes(app: Application): JsValue = JsObject(
Seq(
"id" -> JsNumber(app.getId),
"name" -> JsString(app.getName),
"users" -> toJson(Seq(1, 2, 3))
)
)
}
And then:
scala> toJson(Application(1, "test"))
res1: play.api.libs.json.JsValue = {"id":1,"name":"test","users":[1,2,3]}
Note that you don't need to spell out how to serialize the Seq[Int]—the default Format instances will do the work for you.
Related
I have the following class that's a tree node:
case class Node[A] ( id: Int, data: A, var children: Option[Seq[Node[A]]],
var parent: Option[Node[A]] )
where id is the node id number, data represents information stored in the node, children is the list of children nodes, and parent is the parent node.
I want to generate a json with the tree, so I wrote the following implicit val:
implicit val nodeWrite : Writes[Node[Data]] = (
(JsPath \ "sk").write[Int] and
(JsPath \ "dat").write[Data] and
(JsPath \ "ch").write[Option[Seq[Node[Data]]]] and
(JsPath \ "par").write[Option[Node[Data]]]
) (unlift(Node[Data].unapply))
However the compiler complains:
missing argument list for method apply in object Node Unapplied
methods are only converted to functions when a function type is
expected. You can make this conversion explicit by writing apply _ or
apply(,,,) instead of apply.
How to fix this?
UPDATE
Data is defined as:
case class Data (descrip: String)
UPDATE 2
Since I needed a tree with N roots, I created a Tree class containing a sequence of nodes.
case class Tree[A] ( var nodes: Option[Seq[Node[A]]] )
However I have a problem with serializing the tree:
implicit val treeWrite : Writes[Tree[Data]] = new Writes[Tree[Data]] {
def writes(x: Tree[Data]) = {
Json.obj(
"nodes" -> x.nodes.map(_.map(n => writes(n)))
)
}
}
it throws
type mismatch; found : Option[Nothing] required:
play.api.libs.json.Json.JsValueWrapper
in the x.nodes.map line.
I don't have complete answer, but you can help compiler by specifying types:
(unlift[Node[Data],(Int, Data, Option[Seq[Node[Data]]], Option[Node[Data]])]
(Node.unapply[Data](_)))
But it doesn't help you, since you have to use recursive types with lazyWrite. I would suggest use here more explicit approach:
implicit val nodeWrite : Writes[Node[Data]] = new Writes[Node[Data]] {
def writes(x: Node[Data]) = {
Json.obj(
"id" -> x.id,
"data" -> x.data,
"children" -> x.children.map(_.map(n => writes(n))),
"parent" -> x.parent.map(n => writes(n)))
}
}
val child = Node(1, "child", None, None)
val node = Node(1, "data", Some(List(child)), None)
Json toJson node
res0: play.api.libs.json.JsValue = {"id":1,"data":"data",
"children":[{"id":1,"data":"child","children":null,"parent":null}],"parent":null}
Add null handling and you will be fine.
Compilation error:
No Json serializer found for type Seq[(models.Account, models.Company)]. Try to implement an implicit Writes or Format for this type.
How can I define an implicit writes for the result of a join query?
Controller:
def someEndpoint = Action.async { implicit request =>
val query = for {
a <- accounts if a.id === 10
c <- companies if a.companyID === c.id
} yield (a, c)
db.run(query.result).map(rows => Ok(Json.toJson(rows))) // Causes compilation error
}
Each of my models (account and company) have their own implicit writes (here's the company one):
case class Company(id: Int, name: String)
object Company {
implicit val writes = new Writes[Company] {
def writes(company: Company): JsValue = {
Json.obj(
"id" -> company.id,
"name" -> company.name
)
}
}
}
Is it possible to dynamically handle serializations for joins? I have a lot of things I will be joining together... Do I need to explicitly define a writes for each combination?
Writes.seq will help you
small writer
val w = (
(__ \ "account").write[Account] and
(__ \ "company").write[Company]
).tupled
helps you can transform Seq[(models.Account, models.Company)] to JsValue with
Writes.seq(w).writes(rows)
and last command will be
db.run(query.result).map(rows => Ok(Writes.seq(w).writes(rows))
or a more clear variant
db.run(query.result)
.map(
_.map{
case (a,c) => Json.obj("account" -> a, "company" -> c)
}
)
.map(rows =>
Ok(JsArray(rows))
)
it's the same thing, but you create object for every row yourself.
I think you expect the response of your query in JSON to be something like
[
{
"account" : { "number": "123", "companyID" : 1 },
"company" : { "id" : 1, "name" : "My company"}
} , ...
]
The problem is the response of the query is just a tuple, so "account" and "company" are not easy to calculate.
Instead of a tuple you could create a new case class with the joined data, but I understand you want to avoid that. In that case you can instead of a tuple use a Map that is something that will convert automatically to JSON.
Extra: Creating writers for case classes is very simple
import play.api.libs.json._
implicit val personWrites = Json.writes[Person]
Reference: https://www.playframework.com/documentation/2.4.x/ScalaJsonInception
I have a Json object stored in Mongo like below. It is 'flat', i.e. no nested elements:
{
"key1" : "val1",
"key2" : "val2",
....
"keyn" : "valn"
}
I have fetched it as a JsArray. I also have a case class:
case class IndividualProduct(key1: String, key2: String, ... , key_n: String) {}
In total the Json will have over 40 key/value pairs. Is there a neat way to parse the JsArray into the case class without verbosely referencing the keys?
thanks in advance - Future[Thanks]
import play.api.libs.json._
implicit val reader = Json.reads[IndividualProduct]
val ip = Json.fromJson[IndividualProduct](fetchedJsObj)
That's not a JsArray, but rather a Map[String, String].
So if you have a json like the one you showed, here's what can work:
val json = getYourJsonFromDB()
val kv = json.as[Map[String, String]]
Now you'll be able to do something like this:
val valueForKey13 = kv.get("key13") //returns an Option[String]
Hope this helps
Thanks to the answers to my previous question, I was able to create a function macro such that it returns a Map that maps each field name to its value of a class, e.g.
...
trait Model
case class User (name: String, age: Int, posts: List[String]) extends Model {
val numPosts: Int = posts.length
...
def foo = "bar"
...
}
So this command
val myUser = User("Foo", 25, List("Lorem", "Ipsum"))
myUser.asMap
returns
Map("name" -> "Foo", "age" -> 25, "posts" -> List("Lorem", "Ipsum"), "numPosts" -> 2)
This is where Tuples for the Map are generated (see Travis Brown's answer):
...
val pairs = weakTypeOf[T].declarations.collect {
case m: MethodSymbol if m.isAccessor =>
val name = c.literal(m.name.decoded)
val value = c.Expr(Select(model, m.name))
reify(name.splice -> value.splice).tree
}
...
Now I want to ignore fields that have #transient annotation. How would I check if a method has a #transient annotation?
I'm thinking of modifying the snippet above as
val pairs = weakTypeOf[T].declarations.collect {
case m: MethodSymbol if m.isAccessor && !m.annotations.exists(???) =>
val name = c.literal(m.name.decoded)
val value = c.Expr(Select(model, m.name))
reify(name.splice -> value.splice).tree
}
but I can't find what I need to write in exists part. How would I get #transient as an Annotation so I could pass it there?
Thanks in advance!
The annotation will be on the val itself, not on the accessor. The easiest way to access the val is through the accessed method on MethodSymbol:
def isTransient(m: MethodSymbol) = m.accessed.annotations.exists(
_.tpe =:= typeOf[scala.transient]
)
Now you can just write the following in your collect:
case m: MethodSymbol if m.isAccessor && !isTransient(m) =>
Note that the version of isTransient I've given here has to be defined in your macro, since it needs the imports from c.universe, but you could factor it out by adding a Universe argument if you're doing this kind of thing in several macros.
I was able to perform simple validations on simple json structures like this one:
object RestTest extends Controller {
case class Address(street: String,
number: Int)
case class Person(name: String,
age: Int,
address: Address)
implicit val address = Json.reads[Address]
implicit val rds = Json.reads[Person]
def restTest = Action(parse.json) {
request =>
request.body.validate[Person].map {
case person => Ok(Json.obj("e" -> 0, "message" -> ("The name is: " + person.name + " and he lives in " + person.address.street)))
}.recoverTotal(e => Ok("e" -> 1)
}
}
Now I have the following structure that contains arrays, but I wasn't able to validate it correctly so far. I have tried many different ways, but I keep receiving compilation errors.
case class SecondStructure(index: Int)
case class EntryStructure(field1: String,
muSecondJsonArray: List[SecondStructure])
case class MyJsonArray(allEntries: List[EntryStructure])
How can I validate this json?
Thanks
First of all, ensure you are using the latest Play 2.1.1 releases. There was an issue with earlier versions when validating case classes with a single field. After that, it should all work - please see below for an example:
object JsonTest {
case class SecondStructure(index: Int)
case class EntryStructure(field1: String, muSecondJsonArray: List[SecondStructure])
case class MyJsonArray(allEntries: List[EntryStructure])
// Use the macro "inception" feature to automatically build your Readers.
implicit val ssReads = Json.reads[SecondStructure]
implicit val esReads = Json.reads[EntryStructure]
implicit val arrayReads = Json.reads[MyJsonArray]
// Defining an example instance...
val testArray = MyJsonArray(
List(
EntryStructure("foo", List(SecondStructure(1), SecondStructure(2))),
EntryStructure("bar", List(SecondStructure(3), SecondStructure(4)))))
// And the equivilant JSON structure...
val testJson = Json.obj("allEntries" ->
Json.arr(
Json.obj("field1" -> "foo", "muSecondJsonArray" -> Json.arr(
Json.obj("index" -> 1), Json.obj("index" -> 2))),
Json.obj("field1" -> "bar", "muSecondJsonArray" -> Json.arr(
Json.obj("index" -> 3), Json.obj("index" -> 4)))))
testJson.validate[MyJsonArray].map {
case foo if foo == testArray => println("Okay, we're good!")
}
}