I have the following object:
case class A(id: Long, name:String, lastName:Option[String], age:Int)
I want to use Writes convertor to write only part of this object.
So I tried this code (trying to write the object without the age):
implicit val aWrites: Writes[A] = (
(JsPath \ "it").write[Long] and
(JsPath \ "name").write[String] and
(JsPath \ "lastName"). writeNullable[String]
)(unlift(A.unapply))
But this obviously doesn't compile.
Is there a way to make this work?
You can do this, but you can't reference A.unapply. Part of the conciseness of JSON combinators comes from the apply and unapply methods that are automatically generated by the compiler for case classes. But these methods use all of the parameters in the class.
A.unapply has the signature A => Option[(Long, String, Option[String], Int)]. This is incompatible with combinators that only cover three fields (instead of all four). You have two options.
1) Write another unapply-like method that has the correct signature:
def unapplyShort(a: A): Option[(Long, String, Option[String], Int)] =
Some((a.id, a.name, a.lastName))
implicit val aWrites: Writes[A] = (
(JsPath \ "id").write[Long] and
(JsPath \ "name").write[String] and
(JsPath \ "lastName").writeNullable[String]
)(unlift(unapplyShort))
2) Manually create the Writes as an anonymous class:
implicit val aWrites: Writes[A] = new Writes[A] {
def writes(a: A): JsValue = Json.obj(
"id" -> a.id,
"name" -> a.name,
"lastName" -> a.lastName
)
}
Option 1) from #m-z can be put more succinctly as:
implicit val aWrites: Writes[A] = (
(JsPath \ "id").write[Long] and
(JsPath \ "name").write[String] and
(JsPath \ "lastName").writeNullable[String]
)((a) => (a.id, a.name, a.lastName))
FYI, you can achieve that like the following. (This is just a suggestion.)
object noWrites extends OWrites[Any] {
override def writes(o: Any): JsObject = JsObject(Seq())
}
implicit val aWrites: Writes[A] = (
(JsPath \ "id").write[Long] and
(JsPath \ "name").write[String] and
(JsPath \ "lastName").writeNullable[String] and noWrites // <---
)(unlift(A.unapply))
Related
I have two case classes, User 1 -> N Keyword and I want to query and update with ReactiveMongo in Scala.
Here the classes:
case class Keyword(
keyword: String,
var lastUpdate: Date,
var counter: Int)
object Keyword {
implicit val keywordWriter: OWrites[Keyword] = (
(JsPath \ "keyword").write[String] and
(JsPath \ "lastUpdate").write[Date] and
(JsPath \ "counter").write[Int])(unlift(Keyword.unapply))
implicit val keywordReader: Reads[Keyword] = (
(JsPath \ "keyword").read[String] and
(JsPath \ "lastUpdate").read[Date] and
(JsPath \ "counter").read[Int])(Keyword.apply _)
}
case class User(
_id: Option[BSONObjectID],
name: String,
lastname: String,
twitterUser: String,
var keywords: Option[Seq[Keyword]] = None)
object User {
implicit val userWriter: OWrites[User] = (
(JsPath \ "_id").writeNullable[BSONObjectID] and
(JsPath \ "name").write[String] and
(JsPath \ "lastname").write[String] and
(JsPath \ "twitterUser").write[String] and
(JsPath \ "keywords").writeNullable[Seq[Keyword]])(unlift(User.unapply))
implicit val userReader: Reads[User] = (
(JsPath \ "_id").readNullable[BSONObjectID] and
(JsPath \ "name").read[String] and
(JsPath \ "lastname").read[String] and
(JsPath \ "twitterUser").read[String] and
(JsPath \ "keywords").readNullable[Seq[Keyword]])(User.apply _)
// implicit val userFormatter = Json.format[User]
}
The write part is ok because I see the MongoDB document model that this classes generates, a Nested document list in the keywords field in User collection.
The problem arise in the Read part, when I run this the error is:
play.api.libs.json.JsResultException: JsResultException(errors:List((/keywords(0)/lastUpdate,List(JsonValidationError(List(error.expected.date),WrappedArray()))), (/keywords(1)/lastUpdate,List(JsonValidationError(List(error.expected.date),WrappedArray()))), (/keywords(2)/lastUpdate,List(JsonValidationError(List(error.expected.date),WrappedArray()))), (/keywords(3)/lastUpdate,List(JsonValidationError(List(error.expected.date),WrappedArray()))), (/keywords(4)/lastUpdate,List(JsonValidationError(List(error.expected.date),WrappedArray())))))
at reactivemongo.play.json.JSONSerializationPack$.deserialize(JSONSerializationPack.scala:61)
at reactivemongo.play.json.JSONSerializationPack$.deserialize(JSONSerializationPack.scala:33)
at reactivemongo.core.protocol.ReplyDocumentIterator$.$anonfun$parse$1(protocol.scala:141)
at scala.collection.Iterator$$anon$10.next(Iterator.scala:455)
at scala.collection.Iterator$ConcatIterator.next(Iterator.scala:226)
at reactivemongo.api.DefaultCursor$Impl.$anonfun$headOption$2(DefaultCursor.scala:332)
at scala.concurrent.Future$.$anonfun$apply$1(Future.scala:654)
at scala.util.Success.$anonfun$map$1(Try.scala:251)
at scala.util.Success.map(Try.scala:209)
at scala.concurrent.Future.$anonfun$map$1(Future.scala:288)
at scala.concurrent.impl.Promise.liftedTree1$1(Promise.scala:29)
at scala.concurrent.impl.Promise.$anonfun$transform$1(Promise.scala:29)
at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:60)
at akka.dispatch.BatchingExecutor$AbstractBatch.processBatch(BatchingExecutor.scala:55)
at akka.dispatch.BatchingExecutor$BlockableBatch.$anonfun$run$1(BatchingExecutor.scala:91)
at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:12)
at scala.concurrent.BlockContext$.withBlockContext(BlockContext.scala:81)
at akka.dispatch.BatchingExecutor$BlockableBatch.run(BatchingExecutor.scala:91)
at akka.dispatch.TaskInvocation.run(AbstractDispatcher.scala:40)
at akka.dispatch.ForkJoinExecutorConfigurator$AkkaForkJoinTask.exec(ForkJoinExecutorConfigurator.scala:44)
at akka.dispatch.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
at akka.dispatch.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
at akka.dispatch.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
at akka.dispatch.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)
I also tried just putting:
implicit val keywordFormatter = Json.format[Keyword]
implicit val userFormatter = Json.format[User]
But the same result.
Can someone help me with this?
Thanks in advance!
use BSONDateTime instead of Date
import play.api.libs.json._
import play.api.libs.functional.syntax._
import reactivemongo.bson.BSONDateTime
import reactivemongo.play.json.BSONFormats._
case class Keyword(
keyword: String,
lastUpdate: BSONDateTime,
counter: Int)
object Keyword {
implicit val keywordWriter = new OWrites[Keyword]{
override def writes(keyword: Keyword): JsObject = Json.obj(
"keyword" -> keyword.keyword,
"lastUpdate" -> keyword.lastUpdate,
"counter" -> keyword.counter,
)
}
implicit val keywordReader: Reads[Keyword] = (
(JsPath \ "keyword").read[String] and
(JsPath \ "lastUpdate").read[BSONDateTime] and
(JsPath \ "counter").read[Int])(Keyword.apply _)
}
I am trying to read some JSON String from request and convert them in case class in Play/Scala based REST application.
My code is something like ...
implicit val memberRead: Reads[MemberInfo] = (
(JsPath \ "memberId").readNullable[BigInt] and
(JsPath \ "firstName").read[String] and
(JsPath \ "lastName").read[String] and
(JsPath \ "janrainUUID").readNullable[String] and
(JsPath \ "phones").read[Seq[MemberPhone]] and
(JsPath \ "address").read[Seq[MemberAddress]]
)(MemberInfo.apply _)
implicit val addressRead: Reads[MemberAddress] = (
(JsPath \ "addressId").readNullable[BigInt] and
(JsPath \ "addressType").read[String] and
(JsPath \ "address").read[String] and
(JsPath \ "memberId").read[BigInt]
)(MemberAddress.apply _)
implicit val phoneRead: Reads[MemberPhone] = (
(JsPath \ "phoneId").readNullable[BigInt] and
(JsPath \ "phoneNumber").read[String] and
(JsPath \ "phoneType").read[String] and
(JsPath \ "primaryInd").read[String] and
(JsPath \ "memberId").read[BigInt]
)(MemberPhone.apply _)
But I am getting some compilation error(For all three readNullable[BigInt], memberid in memberRead, addressId in addressRead and phoneId in phoneRead ). Error is ...
No Json deserializer found for type BigInt. Try to implement an implicit Reads or Format for this type.
My Case class are some like this ...
case class MemberInfo(memberId : Option[BigInt],firstName : String, lastName : String,janrainUUID :Option[String] , phones : Seq[MemberPhone],address : Seq[MemberAddress])
case class MemberAddress(addressId:Option[BigInt],addressType:String,address:String,memberId:BigInt)
case class MemberPhone(phoneId : Option[BigInt], phoneNumber:String,phoneType:String,primaryInd:String,memberId:BigInt)
for janrainUUID :Option[String] I am not getting any compilation error , but for BigInt I am getting "No Json deserializer found for type BigInt"
Any one can explain why I am getting this error for BigInt and How can I resolve those? Actually those are PK value when I will do the DB operation for those, so they never may come with request. Is there any way to express that in play/scala like #ignore annotation in jersey.
Any help will be appreciated , thanks a lot...
You need to define serializers for BigInt in the following way:
implicit val BigIntWrite: Writes[BigInt] = new Writes[BigInt] {
override def writes(bigInt: BigInt): JsValue = JsString(bigInt.toString())
}
implicit val BigIntRead: Reads[BigInt] = Reads {
case JsString(value) => JsSuccess(scala.math.BigInt(value))
case JsNumber(value) => JsSuccess(value.toBigInt())
case unknown => JsError(s"Invalid BigInt")
}
Just add this before memberRead serializer and you are good to go and also add error handling for invalid BigInt.
play-json doesn't provide a Reads[BigInt]. It only provides a Reads[BigDecimal].
You can either write your own Reads[BigInt]:
implicit val bigIntReads: Reads[BigInt] = implicitly[Reads[BigDecimal]].map(_.toBigInt())
or use play's Reads[BigDecimal] and transform the result:
implicit val memberRead: Reads[MemberInfo] = (
(JsPath \ "memberId").readNullable[BigDecimal].map(_.toBigInt()) and
...
Edit: Both above solutions have the advantage of not re-inventing the wheel, they build on some well-tested infrastructure provided by play-json. As such they provide benefits that other solutions proposed for this question do not, mainly the correct handling of json string as well as numbers.
You can implement Format like this and use it in your companion object as implicit val:
import play.api.libs.json._
import play.api.libs.functional.syntax._
import scala.util.Try
object BigIntFormat extends Format[BigInt] {
override def reads(json: JsValue): JsResult[BigInt] = json match {
case JsNumber(n) => Try(JsSuccess(n.toBigInt)).getOrElse {
JsError(JsPath() -> JsonValidationError(s"error.expected.numeric(as BigInt), but got '$json'"))
}
case JsString(s) => Try(JsSuccess(BigInt(s))).getOrElse {
JsError(JsPath() -> JsonValidationError(s"error.expected.string(as BigInt), but got '$json'"))
}
case _ => JsError(JsPath() -> JsonValidationError("error.expected.string"))
}
override def writes(o: BigInt): JsValue = JsString(o.toString)
}
case class SomethingWithBigInt(id: BigInt, str: String)
object SomethingWithBigInt {
implicit val bigIntFormatter = BigIntFormat
implicit lazy val format: Format[SomethingWithBigInt] = ({
(JsPath \ "id").format[BigInt] and
(JsPath \ "str").format[String]
})(SomethingWithBigInt.apply, unlift(SomethingWithBigInt.unapply))
}
I am trying to update some code. I am having problem with this case class trying to write the json implicit writer
case class TemplateEmailMessage(recipients: List[EmailRecipient], globalEmailVars: List[(String, String)])
It was like this
implicit val templateEmailMessageWrites = new Writes[TemplateEmailMessage] {
def writes(m: TemplateEmailMessage): JsValue = {
val globalVars: List[JsValue] = m.globalEmailVars.map(g => Json.obj("name" -> g._1, "content" ->g._2))
Json.obj(
"to" -> m.recipients,
"global_merge_vars" -> JsArray(globalVars)
)
}
}
Now is like this. Obviously is not working because the type of the second field List[(String, String)]
object TemplateEmailMessage {
implicit val templateEmailMessageWrites: Writes[TemplateEmailMessage] = (
(JsPath \ "to").write[List[EmailRecipient]] and
(JsPath \ "global_merge_vars").write[List[(String, String)]]
)(unlift(TemplateEmailMessage.unapply))
}
I am not sure how to translate the JsArray, because before it was use to manipulate the values in the writer. I should leave like the old way or there is another way?
Thank you
You can do it like this:
object TemplateEmailMessage{
implicit val templateEmailMessageWrites: Writes[TemplateEmailMessage] = (
(JsPath \ "to").write[List[EmailRecipient]] and
(JsPath \ "global_merge_vars").write[JsArray]
.contramap[List[(String, String)]](list => JsArray(list.map(g => Json.obj("name" -> g._1, "content" ->g._2))))
)(unlift(TemplateEmailMessage.unapply))
}
Other option, is to convert the (String, String) tuple to a case class and create a writer for it.
case class Variable(name: String, content: String)
object Variable{
implicit val variableWrites: Writes[Variable] = (
(JsPath \ "name").write[String] and
(JsPath \ "content").write[String]
)(unlift(Variable.unapply))
}
And then:
implicit val templateEmailMessageWrites: Writes[TemplateEmailMessage] = (
(JsPath \ "to").write[List[EmailRecipient]] and
(JsPath \ "global_merge_vars").write[List[Variable]]
)(unlift(TemplateEmailMessage.unapply))
Lets imagine I have a case class like this:
case class Product(ean: Long, name: String, description: String)
and I want so serialize objects of this class to Json, I can implement the Writes trait like this:
implicit val productWrites: Writes[Product] = (
(JsPath \ "ean").write[Long] and
(JsPath \ "name").write[String] and
(JsPath \ "description").write[String]
)(unlift(Product.unapply))
This works fine if I want to serialize all the attributes of the object. Now lets say I don't want to serialize the ean. I tried something like this:
implicit val productWrites: Writes[Product] = (
(JsPath \ "name").write[String] and
(JsPath \ "description").write[String]
)(unlift(Product.unapply))
This doesn't seem to work since one needs to use all the fields/attributes that the unapply method returns.
Is there a way to make the second serialization method work with only the attributes that I want to serialize or do I have to use something like this:
implicit object ProductWrites extends Writes[Product] {
def writes(p: Product) = Json.obj(
"name" -> Json.toJson(p.name),
"description" -> Json.toJson(p.description)
)
}
Is this the only way?
unlift(Product.unapply) has a type Product => (Long, String, String).
In this case, the argument should have a type Product => (String, String). You can write a function literal like following.
implicit val productWrites: Writes[Product] = (
(JsPath \ "name").write[String] and
(JsPath \ "description").write[String]
)(p => (p.name, p.description))
I think your last example is the way to go. Here's another way of doing the same thing using an implicit val instead of an implicit object:
implicit val productWrites: Writes[Product] = Writes { p =>
Json.obj(
"name" -> Json.toJson(p.name),
"description" -> Json.toJson(p.description)
)
}
I have a Crud Controller in my application which works perfectly with JsonInception, but fail with a custom json converter.
Follow the given case class:
case class Validity(id: Option[UUID], objectType: String, since: DateTime, until: DateTime, objectId: UUID, validityTargetId: UUID, validityTargetType: String)
I have an object companion as follow:
object Validity extends CrudObject[Validity] {
implicit val reads = Json.reads[Validity]
implicit val writes = Json.writes[Validity]
}
Where my CrudObject is a trait with the given code:
trait CrudObject[T] {
val reads: Reads[T]
val writes: Writes[T]
}
This is needed since I'm working in a Generic Crud. Without this, Play is unable to find any implicit converter.
So my Generic Crud Controller, is something like this:
trait CrudController[T] extends Controller {
def service: ServiceModule[T]
def companion: CrudObject[T]
def search...
def insert...
implicit def reads: Reads[T] = companion.reads
implicit def writes: Writes[T] = companion.writes
and for each controller, I have the follow:
object ValidityController extends CrudController[Validity] {
override def service: GenericServiceModule[Validity] = ServiceModule
override def companion: CrudObject[Validity] = Validity
}
Ok, with this design in mind, as I said, works perfectly, I need to design a custom json converter. Yes, I know, it's pretty simple, but something is happening and I don't know how to get rid of that.
Now I'm trying to do the following:
implicit val reads: Reads[Validity] = (
(JsPath \ "id").read[String] and
(JsPath \ "objectType").read[String] and
(JsPath \ "since").read[Long] and
(JsPath \ "until").read[Long] and
(JsPath \ "objectId").read[String] and
(JsPath \ "validityTargetId").read[String] and
(JsPath \ "validityTargetType").read[String]
)(unlift(Validity.apply _))
and it gives me:
Type mismatch, expected: (NotInferedA) => Option[NotInferedB], actual: (CrudObject[Nothing]) => CrudObject[Nothing]
I believe this is happen cuz CrudObject is a trait and does not have apply and unapply.
Anyway, removing CrudObject gives me a similar error:
Type mismatch, expected: (NotInferedA) => Option[NotInferedB], actual: (Option[UUID], String, DateTime, DateTime, UUID, UUID, String) => Validity
but even if I can solve this, I can't imagine living without CrudObject due my GenericCrud.
Any Thoughts?
PS: Thanks to #m-z who has been giving me some assist through stackoverflow =)
UPDATE
I'm almost there with this approach:
implicit object validityFormat extends Format[Validity] {
override def writes(o: Validity): JsValue = {
Json.obj(
"id" -> JsString(o.id.getOrElse(null).toString),
"objectType" -> JsString(o.objectType),
"since" -> JsString(o.since.toString),
"until" -> JsString(o.since.toString),
"objectId" -> JsString(o.objectId.toString),
"validityTargetId" -> JsString(o.validityTargetId.toString),
"validityTargetType" -> JsString(o.validityTargetType))
}
override def reads(json: JsValue): JsResult[Validity] = {
JsSuccess(Validity(
(json \ "id").as[Option[UUID]],
(json \ "objectType").as[String],
(json \ "since").as[DateTime],
(json \ "until").as[DateTime],
(json \ "objectId").as[UUID],
(json \ "validityTargetId").as[UUID],
(json \ "validityTargetType").as[String])
)
}
}
Using this way, which is different from documentation Scala Combinators, I don't get the previous error, which is good, no type mismatch =)
Now I'm working out on to figure out how to convert to UUID and DateTime.
UPDATE
I did an example that works, but I accepted the #m-z answer because there are less boilerplate than mine, but both worked fine. The big difference is, in my approach I needed to provide some custom converters for DateTime and for UUID, whereas #m-z approach you don't!
implicit object UUIDFormatter extends Format[UUID] {
override def reads(json: JsValue): JsResult[UUID] = {
val uuid = json.validate[String]
JsSuccess(UUID.fromString(uuid.get))
}
override def writes(o: UUID): JsValue = {
JsString(o.toString)
}
}
implicit object DateTimeFormatter extends Format[DateTime] {
override def reads(json: JsValue): JsResult[DateTime] = {
val datetime = json.validate[Long]
JsSuccess(new DateTime(datetime.get))
}
override def writes(o: DateTime): JsValue = {
JsNumber(o.getMillis)
}
}
implicit object validityFormat extends Format[Validity] {
override def writes(o: Validity): JsValue = {
Json.obj(
"id" -> JsString(o.id.getOrElse(null).toString),
"objectType" -> JsString(o.objectType),
"since" -> JsNumber(o.since.getMillis),
"until" -> JsNumber(o.since.getMillis),
"objectId" -> JsString(o.objectId.toString),
"validityTargetId" -> JsString(o.validityTargetId.toString),
"validityTargetType" -> JsString(o.validityTargetType))
}
override def reads(json: JsValue): JsResult[Validity] = {
JsSuccess(Validity(
(json \ "id").as[Option[UUID]],
(json \ "objectType").as[String],
(json \ "since").as[DateTime],
(json \ "until").as[DateTime],
(json \ "objectId").as[UUID],
(json \ "validityTargetId").as[UUID],
(json \ "validityTargetType").as[String])
)
}
There are a couple problems here, but neither of them are relevant to generics, because you're dealing with the concrete type Validity.
First, the last argument using Reads combinators should be (Validity.apply _). You would only use unlift with Writes.
Second, the types in the combinators must map to the types in your Validity class.
implicit val reads: Reads[Validity] = (
(JsPath \ "id").readNullable[UUID] and // readNullable reads to Option
(JsPath \ "objectType").read[String] and
(JsPath \ "since").read[DateTime] and
(JsPath \ "until").read[DateTime] and
(JsPath \ "objectId").read[UUID] and
(JsPath \ "validityTargetId").read[UUID] and
(JsPath \ "validityTargetType").read[String]
)(Validity.apply _)
Reads already exist for UUID and DateTime, so this should work okay.
Similarly, Writes[Validity] would look like this:
implicit val writes: Writes[Validity] = (
(JsPath \ "id").writeNullable[UUID] and
(JsPath \ "objectType").write[String] and
(JsPath \ "since").write[DateTime] and
(JsPath \ "until").write[DateTime] and
(JsPath \ "objectId").write[UUID] and
(JsPath \ "validityTargetId").write[UUID] and
(JsPath \ "validityTargetType").write[String]
)(unlift(Validity.unapply))
Hi I Think that your case class has an option parameter your Reads must have readnullable in the optional parameters must be like this , I'm not sure with UIID and String
case class Validity(id: Option[UUID], objectType: String, since: DateTime, until: DateTime, objectId: UUID, validityTargetId: UUID, validityTargetType: String)
implicit val reads: Reads[Validity] = (
(JsPath \ "id").readNullable[UIID] and
(JsPath \ "objectType").read[String] and
(JsPath \ "since").read[Long] and
(JsPath \ "until").read[Long] and
(JsPath \ "objectId").read[String] and
(JsPath \ "validityTargetId").read[String] and
(JsPath \ "validityTargetType").read[String]
)(unlift(Validity.apply _))