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 _)
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.
I wanna write JSON validation for a few Scala model classes in Play framework 2.3x. I'm using JSON Reads to do that following the instructions (https://playframework.com/documentation/2.3.x/ScalaJsonCombinators). But I get "Application does not take parameters" error and I don't know how to fix this.
Here is my code.
package models
import play.api.libs.json._
import play.api.libs.json.Reads._
import play.api.libs.functional.syntax._
import reactivemongo.bson.BSONObjectID
import java.util.Date
case class ArtifactModel(
_id: BSONObjectID,
name: String,
createdAt: Date,
updatedAt: Date,
attributes: List[AttributeModel],
stateModels: List[StateModel])
case class AttributeModel(
name: String,
comment: String)
case class StateModel(
name: String,
comment: String)
object ArtifactModel {
implicit val artifactModelReads: Reads[ArtifactModel] = (
(__ \ "_id").readNullable[String] ~
(__ \ "name").read[String] ~
(__ \ "createdAt").readNullable[Long] ~
(__ \ "updatedAt").readNullable[Long] ~
(__ \ "attributes").read[List[AttributeModel]] ~
(__ \ "stateModels").read[List[StateModel]]
)(ArtifactModel) // here is the error: "Application does not take parameters"
implicit val attributeModelReads: Reads[AttributeModel] = (
(__ \ "name").read[String] ~
(__ \ "comment").read[String]
)(AttributeModel)
implicit val stateModelReads: Reads[StateModel] = (
(__ \ "name").read[String] ~
(__ \ "comment").read[String]
)(StateModel)
}
Can you help me? Any solution or suggestions for JSON validation in Scala/Play are appreciated.
The types of the Reads object are not the same as those the apply method takes. E.g., readNullable[String] results Option[String], not String. Same for the BSONObjectId and the Date. This compiles, but you probably need to use some maps:
implicit val artifactModelReads: Reads[ArtifactModel] = (
(__ \ "_id").read[BSONObjectID] ~
(__ \ "name").read[String] ~
(__ \ "createdAt").read[Date] ~
(__ \ "updatedAt").read[Date] ~
(__ \ "attributes").read[List[AttributeModel]] ~
(__ \ "stateModels").read[List[StateModel]]
)(ArtifactModel.apply _)
You can after a read, like so (CONVERT_TO_DATE is imaginary):
implicit val artifactModelReads: Reads[ArtifactModel] = (
(__ \ "_id").read[BSONObjectID] ~
(__ \ "name").read[String] ~
(__ \ "createdAt").read[String].map( s=>CONVERT_TO_DATE(s) ) ~
(__ \ "updatedAt").read[Date] ~
(__ \ "attributes").read[List[AttributeModel]] ~
(__ \ "stateModels").read[List[StateModel]]
)(ArtifactModel.apply _)
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)
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.