How to use specific apply method in implicit Json `reads` from Scala - json

I have a class that takes a few optional Enumeration types in its constructor:
case class GPMedia(id: Option[GPID], created: Option[DateTime], active: Option[Boolean], data: Array[Byte], mimeType: Option[GPMediaType.Type], encoding: Option[GPEncoder.Codec], compression: Option[GPCompressor.Type])
I've been struggling to create an implicit Json reads method that works. I keep ending up with errors such as:
[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/utility/GPMedia.scala:57: overloaded method value apply with alternatives:
etc...
What I'm trying to do is translate the inbound Json strings, turning them into the right kind of Option instance (eg., a MIME type "image/png" in the Json would turn into Option(GPMediaType(v)). The GPMediaType constructor will map the string, and return a correct value (one of which is GPMediaType.Unknown).
Here's the implicit reads that I've worked up so far, implemented on the GPMedia class' companion object...
case object GPMedia extends GPRequestLogging {
implicit val reads: Reads[GPMedia] = (
(__ \ "id").readNullable[GPID] and
(__ \ "created").readNullable[DateTime] and
(__ \ "active").readNullable[Boolean] and
(__ \ "data").read[Array[Byte]] and
(__ \ "mimeType").readNullable[String].map(v => Option(GPMediaType(v))) and
(__ \ "encoding").readNullable[String].map(v => Option(GPEncoder(v.get))) and
(__ \ "compression").readNullable[String].map(v => Option(GPCompressor(v.get)))
)(GPMedia.apply _)
}
This works, but when I try to add other apply() methods, it all goes to heck. How can I apply a specific apply method in the Json reads implementation? For example, when I add this apply method:
def apply(data: Array[Byte]) = new GPMedia(None, None, None, data, None, None, None)
I end up with:
[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/utility/GPMedia.scala:60: ambiguous reference to overloaded definition,
[error] both method apply in object GPMedia of type (id: Option[models.GPID], created: Option[org.joda.time.DateTime], active: Option[Boolean], data: Array[Byte], mimeType: Option[utility.GPMediaType.Type], encoding: Option[utility.GPEncoder.Codec], compression: Option[utility.GPCompressor.Type])utility.GPMedia
[error] and method apply in object GPMedia of type (data: Array[Byte])utility.GPMedia
[error] match expected type ?
[error] )(GPMedia.apply _)
I've tried a few different approaches, such as (GPMedia.apply(...)) but I can't seem to get the parameters right.
I'm new to the whole Json implicit reader/writer and Json decoding syntax. Clearly I'm missing something here...
Edit
Here's another example, regarding my attempt to call a specific apply method:
implicit val reads: Reads[GPMedia] = (
(__ \ "id").readNullable[GPID] and
(__ \ "created").readNullable[DateTime] and
(__ \ "active").readNullable[Boolean] and
(__ \ "data").read[Array[Byte]] and
(__ \ "mimeType").readNullable[String].map(v => Option(GPMediaType(v))) and
(__ \ "encoding").readNullable[String].map(v => Option(GPEncoder(v.get))) and
(__ \ "compression").readNullable[String].map(v => Option(GPCompressor(v.get)))
)(v => GPMedia.apply(v.id, v.created, v.active, v.data, v.mimeType, v.encoding, v.compression))
This results in:
[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/utility/GPMedia.scala:60: type mismatch;
[error] found : utility.GPMedia
[error] required: (Option[models.GPID], Option[org.joda.time.DateTime], Option[Boolean], Array[Byte], Option[utility.GPMediaType.Value], Option[utility.GPEncoder.Value], Option[utility.GPCompressor.Type])
[error] (which expands to) (Option[models.GPID], Option[org.joda.time.DateTime], Option[Boolean], Array[Byte], Option[utility.GPMediaType.Value], Option[utility.GPEncoder.Value], Option[utility.GPCompressor.Value])
[error] )(v => GPMedia.apply(v.id, v.created, v.active, v.data, v.mimeType, v.encoding, v.compression))
[error] ^

It is possible.
But You need to specify all the parameters required for the overloaded apply method using _: ParameterType like I have done below and it will work.
implicit val reads: Reads[GPMedia] = (
(__ \ "id").readNullable[GPID] and
(__ \ "created").readNullable[DateTime] and
(__ \ "active").readNullable[Boolean] and
(__ \ "data").read[Array[Byte]] and
(__ \ "mimeType").readNullable[String].map(v => Option(GPMediaType(v))) and
(__ \ "encoding").readNullable[String].map(v => Option(GPEncoder(v.get))) and
(__ \ "compression").readNullable[String].map(v => Option(GPCompressor(v.get)))
) (
v => GPMedia.apply(
_: GPID,
_: DateTime,
_: Boolean,
_: Array[Byte],
_: Option[GPMediaType],
_: Option[GPEncoder],
_: Option[GPCompressor]
)
)

This is not possible. The compiler does not know which apply method to use. This is just one of the caveats of using overloaded methods. The only way you can make this like "nice" is to rename the methods, or alias the overloaded apply methods with different names and use those.
Your second attempt does not work because the compiler is expecting a function with a signature similar to apply, like:
(Option[GPID], Option[DateTime], Option[Boolean], Array[Byte], Option[String], Option[String], Option[String]) => GPMedia
But you're trying to use:
GPMedia => GPMedia
Which doesn't work, because we don't yet have a GPMedia object, just the tupled fields. It would look more like:
implicit val reads: Reads[GPMedia] = (
(__ \ "id").readNullable[GPID] and
(__ \ "created").readNullable[DateTime] and
(__ \ "active").readNullable[Boolean] and
(__ \ "data").read[Array[Byte]] and
(__ \ "mimeType").readNullable[String].map(v => Option(GPMediaType(v))) and
(__ \ "encoding").readNullable[String].map(v => Option(GPEncoder(v.get))) and
(__ \ "compression").readNullable[String].map(v => Option(GPCompressor(v.get)))
).tupled.map(v => GPMedia.apply(v._1, v._2, v._3, v._4, v._5, v._6, v._7))
Which does not look good. Usually we can make it look better like this:
implicit val reads: Reads[GPMedia] = (
(__ \ "id").readNullable[GPID] and
(__ \ "created").readNullable[DateTime] and
(__ \ "active").readNullable[Boolean] and
(__ \ "data").read[Array[Byte]] and
(__ \ "mimeType").readNullable[String].map(v => Option(GPMediaType(v))) and
(__ \ "encoding").readNullable[String].map(v => Option(GPEncoder(v.get))) and
(__ \ "compression").readNullable[String].map(v => Option(GPCompressor(v.get)))
).tupled.map(v => GPMedia.apply _ tupled v)
Except that you will end up with the same problem that you started with, because the compiler will not be able to choose the correct apply method. So you really have no choice but to rename or make things ugly.

Related

Scala json parsing for single field class

I have following class and Reads Writes for json parsing / writeing
import play.api.libs.json._
import play.api.libs.functional.syntax._
import scala.collection.immutable.{Seq => ISeq, Set => ISet}
case class FileExcInc( isExclude: Boolean, isFile: Boolean, pattern: String) { }
implicit val fEIReads: Reads[FileExcInc] = (
(__ \ "isExclude").read[Boolean] and
(__ \ "isFile").read[Boolean] and
(__ \ "pattern").read[String]
)(FileExcInc.apply _)
implicit val fEIWrites: Writes[FileExcInc] = (
(__ \ "isExclude").write[Boolean] and
(__ \ "isFile").write[Boolean] and
(__ \ "pattern").write[String]
)(unlift(FileExcInc.unapply))
case class DirConf(sourceDir: String, csdataFile: String, fileExcIncSeq: ISeq[FileExcInc]) { }
implicit val dCReads: Reads[DirConf] = (
(__ \ "sourceDir").read[String] and
(__ \ "csdataFile").read[String] and
(__ \ "fileExcIncSeq").read[ISeq[FileExcInc]]
)(DirConf.apply _)
implicit val dCWrites: Writes[DirConf] = (
(__ \ "sourceDir").write[String] and
(__ \ "csdataFile").write[String] and
(__ \ "fileExcIncSeq").write[ISeq[FileExcInc]]
)(unlift(DirConf.unapply))
This compiles OK, but the following does not:
case class Conf(dirConfSeq: ISeq[DirConf]) { }
implicit val cReads: Reads[Conf] = (
(__ \ "dirConfSeq").read[ISeq[DirConf]]
)(Conf.apply _)
implicit val cWrites: Writes[Conf] = (
(__ \ "dirConfSeq").write[ISeq[DirConf]]
)(unlift(Conf.unapply))
It produces following error:
[info] Compiling 17 Scala sources and 1 Java source to /home/jk/workspace/Gen/target/scala-2.12/classes...
[error] /home/jk/workspace/Gen/src/main/scala/com/example/CTool.scala:341: overloaded method value read with alternatives:
[error] (t: scala.collection.immutable.Seq[com.example.CTool.DirConf])play.api.libs.json.Reads[scala.collection.immutable.Seq[com.example.CTool.DirConf]] <and>
[error] (implicit r: play.api.libs.json.Reads[scala.collection.immutable.Seq[com.example.CTool.DirConf]])play.api.libs.json.Reads[scala.collection.immutable.Seq[com.example.CTool.DirConf]]
[error] cannot be applied to (scala.collection.immutable.Seq[com.example.CTool.DirConf] => com.example.CTool.Conf)
[error] (__ \ "dirConfSeq").read[ISeq[DirConf]]
[error] ^
[error] /home/jk/workspace/Gen/src/main/scala/com/example/CTool.scala:345: overloaded method value write with alternatives:
[error] (t: scala.collection.immutable.Seq[com.example.CTool.DirConf])(implicit w: play.api.libs.json.Writes[scala.collection.immutable.Seq[com.example.CTool.DirConf]])play.api.libs.json.OWrites[play.api.libs.json.JsValue] <and>
[error] (implicit w: play.api.libs.json.Writes[scala.collection.immutable.Seq[com.example.CTool.DirConf]])play.api.libs.json.OWrites[scala.collection.immutable.Seq[com.example.CTool.DirConf]]
[error] cannot be applied to (com.example.CTool.Conf => scala.collection.immutable.Seq[com.example.CTool.DirConf])
[error] (__ \ "dirConfSeq").write[ISeq[DirConf]]
What's wrong in the last Reads, Writes? How to create Reads, Writes for class having just one field?
To create a Reads/Writes for a case class with just one field, use map and contramap to convert the field to and from the case class.
implicit val cReads: Reads[Conf] =
(__ \ "dirConfSeq").read[ISeq[DirConf]].map(Conf)
implicit val cWrites: Writes[Conf] =
(__ \ "dirConfSeq").write[ISeq[DirConf]].contramap(_.dirConfSeq)
Even easier is to use the Json.format macro to generate all of the Reads/Writes boilerplate. It handles case classes with any number of fields provided the field names match the JSON. A Format[T] is both a Reads[T] and a Writes[T].
implicit val fEIFormat: Format[FileExcInc] = Json.format[FileExcInc]
implicit val dCFormat: Format[DirConf] = Json.format[DirConf]
implicit val cFormat: Format[Conf] = Json.format[Conf]
See: https://www.playframework.com/documentation/2.6.x/ScalaJsonAutomated

Scala/Play How to use JSON read to handle JSON value that can be string or array and always return Set[String]

I have the following class in my play server and the JSON Reads used for validation
case class Foo(name: String, link: String)
val fooReads = (
(__ \ "name").read[String] ~
(__ \ "link").read[String]
)(Foo.apply _)
I have changed the model to allow multiple links instead of one so i can expect array of strings in the input JSON:
case class Foo(name: String, link: Set[String])
val fooReads = (
(__ \ "name").read[String] ~
(__ \ "link").read[Set[String]] // it works this way
)(Foo.apply _)
I want to make it back compatible so it can handle JSON value of "link" with string along with array of strings
You can use the operator orElse on json Reads to create your custom reader:
val readsSet: Reads[Set[String]] = Reads.of[Set[String]].orElse(Reads.of[String].map(str => Set(str)))
val fooReads = (
(__ \ "name").read[String] ~
(__ \ "link").read[Set[String]](readsSet)
)(Foo.apply _)
You can use Reads.orElse combinator:
val fooReads: Reads[Foo] = (
(__ \ "name").read[String] ~
(__ \ "link").read(Reads.of[String].map(str => Set(str)).
orElse(Reads.of[Set[String]]))
)(Foo.apply _)

setting default values with Play! Json Combinators

I'm using play!'s json combinators to validate, read and write JSON. Is it possible to specify default values in reads or writes if they are not set?
validation of json is done like this (where json is a JsValue):
json.validate[Pricing]
My code is:
case class Pricing(
_id: ObjectId = new ObjectId,
description: String,
timeUnit: TimeUnit.Value,
amount: Double = 0.0) {
#Persist val _version = 1
}
my reads and writes:
implicit val pricingReads: Reads[Pricing] = (
(__ \ "_id").read[ObjectId] and
(__ \ "description").read[String] and
(__ \ "timeUnit").read[TimeUnit.Value] and
(__ \ "amount").read[Double]
)(Pricing.apply _)
implicit val pricingWrites: Writes[Pricing] = (
(__ \ "_id").write[ObjectId] and
(__ \ "description").write[String] and
(__ \ "timeUnit").write[TimeUnit.Value] and
(__ \ "amount").write[Double]
)(unlift(Pricing.unapply))
so if I would recieve a Json like:
{"description": "some text", "timeUnit": "MONTH"}
I get errors, that fields _id and amount are missing. Is there any possiblity to set the default values without adding it directy to the JsValue?
Thanks in advance!
I'd rather use Options:
case class Pricing(
_id: Option[ObjectId],
description: String,
timeUnit: TimeUnit.Value,
amount: Option[Double]) {
#Persist val _version = 1
}
and replace your pricingReads with this:
implicit val pricingReads: Reads[Pricing] = (
(__ \ "_id").readNullable[ObjectId] and
(__ \ "description").read[String] and
(__ \ "timeUnit").read[TimeUnit.Value] and
(__ \ "amount").readNullable[Double]
)(Pricing.apply _)
Then your code will work on missing fields and yo will be able to do this:
_id.getOrElse(new ObjectId)

Play Framework 2.1.1 Json Writes nested objects

I've been trying to get my head around this all afternoon to no avail, Play 2.1.1 overhauled how the Json reads and writes work.
Basically I have a wrapper object that looks like:
case class CombinedUser(user: SocialUser, userdetails: UserDetails)
as you can see it contains 2 classes that I want to serialize and deserialize to json.
But I don't understand how I can get and set the fields in the subclasses using the new design.
For example
implicit val combinedUser2Reads = (
(__ \ "email").read[String] and
(__ \ "providerid").read[String] and
(__ \ "firstname").read[String] and
(__ \ "lastname").read[String] and
(__ \ "fullname").read[String] and
(__ \ "avatarurl").read[String] and
(__ \ "address1").read[String] and
(__ \ "address2").read[String] and
(__ \ "address3").read[String] and
(__ \ "city").read[String] and
(__ \ "country").read[String] and
(__ \ "phone").read[String] and
(__ \ "publickey").as[String]
)(CombinedUser2.apply _)
I want a json blob that has most of the subclasses fields, all strings.
Because it uses the apply stuff, I don't see how I can create the subclasses before the mapping.
Any help or guidance is much appreciated.
Thanks
Tom
You can do it this way:
case class SocialUser(firstName: String, lastName: String)
case class UserDetails(avatarUrl: String, phone: String)
case class CombinedUser(user: SocialUser, userDetails: UserDetails)
implicit val combinedUserReads: Reads[CombinedUser] = (
(__ \ "user").read((
(__ \ "firstName").read[String] and
(__ \ "lastName").read[String]
)(SocialUser)) and
(__ \ "userDetails").read((
(__ \ "avatarUrl").read[String] and
(__ \ "phone").read[String]
)(UserDetails))
)(CombinedUser)
However its better to create separated Reads:
implicit val socialUserReads = (
(__ \ "firstName").read[String] and
(__ \ "lastName").read[String]
)(SocialUser)
implicit val userDetailsReads = (
(__ \ "avatarUrl").read[String] and
(__ \ "phone").read[String]
)(UserDetails)
implicit val combinedUserReads: Reads[CombinedUser] = (
(__ \ "user").read[SocialUser] and
(__ \ "userDetails").read[UserDetails]
)(CombinedUser)
Edit: for simple case classes, its possible to do:
implicit val socialUserReads = Json.format[SocialUser]
implicit val userDetailsReads = Json.format[UserDetails]
implicit val combinedUserReads = Json.format[CombinedUser]
Here is quite comprehensive introduction to JSON Reads and more.
How about partial objects? If I don't want to fill in every field in
the constructor, can I pass empties or do I overload the constructor
or similar?
Use Option:
case class CombinedUser(user: SocialUser, userDetails: Option[UserDetails])
//reads
implicit val combinedUserReads: Reads[CombinedUser] = (
(__ \ "user").read[SocialUser] and
(__ \ "userDetails").readOpt[UserDetails]
)(CombinedUser)
//writes
implicit val combinedUserWrites: Writes[CombinedUser] = (
//socialUserWrites and userDetailsWrites must be in scope
(__ \ "user").write[SocialUser] and
(__ \ "userDetails").write[Option[UserDetails]]
)(unlift(CombinedUser.unapply))
val json = Json.obj(
"user" -> Json.obj(
"firstName" -> "Homer",
"lastName" -> "Simpson"
)
)
Json.fromJson[CombinedUser](json)
//JsSuccess(CombinedUser(SocialUser(Homer,Simpson),None),)

type mismatch error when creating Reads for Play 2.1

I have been playing with this for hours. I have been trying different methods of creating a read and I am just completely stumped.
I am on Play 2.1.0 and Scala 2.10.1
ERROR:
type mismatch; found : models.Registration.type required: play.api.libs.json.Reads[?]
Code:
package models
import play.api.libs.json._
import play.api.libs.functional.syntax._
case class Registration(
user: (String,String,String,String,String,String)
)
object RegistrationHelper {
implicit val regReads: Reads[Registration] = (
(__ \ "user").read(
(__ \ "id").read[String] and
(__ \ "username").read[String] and
(__ \ "first_name").read[String] and
(__ \ "last_name").read[String] and
(__ \ "email_address").read[String] and
(__ \ "user_avatar").read[String]
tupled
)
)(Registration) //!!ERROR ON THIS LINE
}
JSON:
{
user: {
id: "35fc8ba5-56c3-4ebe-9a21-489a1a207d2e",
username: "flastname",
first_name: "Firstname",
last_name: "Lastname",
email_address: "foo#bar.com",
user_avatar: "http://blog.ideeinc.com/wp-content/uploads/2010/04/tineye-robot.jpg"
}
}
This should work:
implicit val regReads: Reads[Registration] = (__ \ "user").read(
(__ \ "id").read[String] and
(__ \ "username").read[String] and
(__ \ "first_name").read[String] and
(__ \ "last_name").read[String] and
(__ \ "email_address").read[String] and
(__ \ "user_avatar").read[String]
tupled
) map Registration.apply _
See this question for more information.