I have the following JSON reader in Play 2.3:
import play.api.libs.json._
import play.api.libs.json.Reads._
val airportSearchReads: Reads[String] = (JsPath \ "search").read[String](minLength(3))
and the compiler gives me the error
diverging implicit expansion for type play.api.libs.json.Reads[M]
starting with method ArrayReads in trait DefaultReads
if I use an implicit val I get
ambiguous implicit values:
both value uuidReads in trait DefaultReads of type => play.api.libs.json.Reads[java.util.UUID]
and value airportSearchReads in object AirportSearch of type => play.api.libs.json.Reads[String]
match expected type play.api.libs.json.Reads[M]
How do I get it to work?
I get a different error, but it works fine for me if I add an explicit type parameter to minLength:
scala> val airportSearchReads: Reads[String] = (JsPath \ "search").read[String](minLength[String](3))
airportSearchReads: play.api.libs.json.Reads[String] = play.api.libs.json.Reads$$anon$8#3fee86da
I think the problem with leaving that up to the compiler is that there are different combinations of implicits in scope that would satisfy the implicit parameter list of minLength.
DefaultReads provides readers you need for transforming json values to common types (String, Option, Array, etc.). So providing new readers for String is not necessary.
Hence, for accessing a field in your json object you don't need to define a reader, unless you want to read that field into an arbitrary type of yours.
All you need in this case is the constraint which is defined both in Reads and Constraints. So assuming that your json object is jsValue the following code gives you what you want:
// val jsValue = ...
(jsValue \ "search").as[String](Reads.minLength(3))
Related
We have a case class and Json combinators similar to the below:
case class Thing(param1: Option[BigDecimal],
param2: Option[BigDecimal])
object Thing {
implicit val writes: Writes[Thing] = Json.writes[Thing]
implicit val reads: Reads[Thing] = (
(__ \ "parent" \ "param1").readNullable[BigDecimal] and
(__ \ "param2").readNullable[BigDecimal]
)(Thing.apply _)
The Json reads combinator for Thing will handle incoming Json matching the below:
{
"parent": {
"param1: 1111.11
},
"param2": 2222.22
}
The standard ReadNullable works as we need it to when param1 is not present, resulting in the creation of Thing(None, 2222.22). However the parent object is also optional, and reading the path explicitly like the above results in a path missing exception if it does not exist. We need this to result in a Thing(None, 2222.22), the same as if the param1 field wasn't present.
We could use a recursive read such as (__ \\ "param1") to bipass this exception, but ideally we would like to preserve the explicit path as there are other Json objects that have similar, if not identical fields at the same level.
Is this possible by using Json combinators in this way?
You can do in following way by creating two case classes.
case class Thing(param1: Option[BigDecimal])
case class ParentJson(parent: Option[Thing], param2: Option[BigDecimal])
You can replace .readNullable[BigDecimal] with read(Reads.optionNoError[BigDecimal]) to map errors to None.
Hi everyone recently I faced an issue in converting json into my own data model.
I have a json format message which may contain an empty string:
{
"name" : "John Doe",
"hobbies": ""
}
or a list of hobby types:
{
"name" : "John Doe",
"hobbies": [{"name":"basketball"}]
}
And the following is my case class data model in scala play framework:
case class Person(name: String, hobbies: List[Hobby])
case class Hobby(name: String)
Right now I'm using the default json formatter but of course it's not working well when we have empty string as value.
implicit val HobbyJson= Json.format[Hobby]
implicit val PersonJson = Json.format[Person]
it will throw exception if the hobbies has a empty string. I want to convert it into an empty list when it's the empty string. I search the document Play provides but couldn't find infomation. Can anyone give some suggestions?
Thanks in advance.
As you mentioned, the default Format macros won't work for you here because of the inconsistent treatment of hobbies. So you need to implement your own Reads[Person] - here's how I'd do it:
object PersonJson {
implicit val hobbyConverter = Json.format[Hobby]
val personReads = new Reads[Person] {
override def reads(json: JsValue): JsResult[Person] = {
for {
personName <- (json \ "name").validate[String]
hobbies <- (json \ "hobbies").validate[JsValue]
} yield {
val maybeHobbyList = hobbies.validate[List[Hobby]].asOpt
Person(personName, maybeHobbyList.getOrElse(Nil))
}
}
}
implicit val personConverter = Format(personReads, Json.writes[Person])
}
The key thing to note here is surrounding the whole thing in a JsResult courtesy of the for-comprehension and the yield. This gives us all the necessary checking (like the name field being there and being a String, and the hobbies field being there).
The code within the yield block only runs if we've got something that looks pretty close to a Person. Then we can safely try validating the hobbies as a List[Hobby], and convert the result to an Option[List[Hobby]]. It'll be a None if it didn't work (thus it must have been a string) and so we default it to the empty list as required.
Thanks #millhouse answer, it definitely works. Like he said we need a custom Reads[Person] to properly convert it.
I also post my code as reference.
implicit val personJsonReads: Reads[Person] = (
(__ \ "name").read[String] and
(__ \ "hobbies").read[List[Hobby]].orElse(Reads.pure(List()))
) (Person.apply _)
read[List[Hobby]].orElse(Reads.pure(List())) will generate the empty list when the value cannot convert to List[Hobby].
I am new to Scala and Play, and I ask for help with this simple example. I tried to search for solution by myself, but I did not succeed.
I am trying to do the example from from Mastering Play Framework for Scala book, the one about extending Json parser (Pages 29-30).
The environment I use is:
Scala: 2.11.7
Play: 2.5.8
Activator: 1.3.10
The code is:
case class Subscription(emailId: String, interval: Long)
In controller:
import play.api.libs.json.Json
import play.api.libs.json.JsValue
import play.api.libs.json.Writes
.....
val parseAsSubscription = parse.using {
request =>
parse.json.map {
body =>
val emailId:String = (body \ "emailId").as[String]
val fromDate:Long = (body \ "fromDate").as[Long]
Subscription(emailId, fromDate)
}
}
implicit val subWrites:Writes[Subscription] = Json.writes[Subscription]
def getSub = Action(parseAsSubscription) {
request =>
val subscription: Subscription = request.body
Ok(Json.toJson(Subscription))
}
The line: Ok(Json.toJson(Subscription)) gives an error
No Json serializer found for type models.Subscription.type. Try to
implement an implicit Writes or Format for this type.
This is odd, because Writes object is defined one row above. Thus, I tried to pass it to toJson method explicitly:
Ok(Json.toJson(Subscription)(subWrites))
It gave me a different error, which partially explained why existing Writes object did not suit:
type mismatch;
found:
play.api.libs.json.Writes[models.Subscription]
required:
play.api.libs.json.Writes[models.Subscription.type]
However, I don't understand the nature of this error and what models.Subscription.type is .
I used to do a similar thing in a different example, and it worked just fine.
Any help will be appreciated.
You're trying to serialize the type Subscription, rather than the request body, which you stored as the value subscription. Try replacing the last line with Ok(Json.toJson(subscription)).
Would anyone please explain why the following happens?
scala> import play.api.libs.json._
scala> Json.toJson("""{"basic":"test"}""") // WORKS CORRECTLY
res134: play.api.libs.json.JsValue = "{\"basic\":\"test\"}"
scala> Json.toJson(""" {"basic":"test"} """) \ "basic" // ??? HOW COME?
res131: play.api.libs.json.JsValue = JsUndefined('basic' is undefined on object: " {\"basic\":\"test\"} ")
Many thanks
Json.toJson renders its argument as a JSON value using an implicitly provided Writes instance. If you give it a string, you'll get a JsString (typed as a JsValue). You want Json.parse, which parses its argument:
scala> Json.parse("""{"basic":"test"}""") \ "basic"
res0: play.api.libs.json.JsValue = "test"
As expected.
And to address your answer (which should be a comment or a new question, by the way), if you give toJson a value of some type A, it will convert it into a JSON value, assuming that there's an instance of the Writes type class in scope for that A. For example, the library provides Writes[String], Writes[Int], etc., so you can do the following:
scala> Json.prettyPrint(Json.toJson(1))
res11: String = 1
scala> Json.prettyPrint(Json.toJson("a"))
res12: String = "a"
scala> Json.prettyPrint(Json.toJson(List("a", "b")))
res13: String = [ "a", "b" ]
You can also create Writes instances for your own types (here I'm using Play's "JSON inception"):
case class Foo(i: Int, s: String)
implicit val fooWrites: Writes[Foo] = Json.writes[Foo]
And then:
scala> Json.prettyPrint(Json.toJson(Foo(123, "foo")))
res14: String =
{
"i" : 123,
"s" : "foo"
}
Using type classes to manage encoding and decoding is an alternative to reflection-based approaches, and it has a lot of advantages (but that's out of the scope of this question).
Turning my comment into an answer:
Json.toJson() does not create an object. It turns an object into a JSON string. What I think you're wanting is Json.parse(). Once you've parsed a JSON string, it's an object, and you can get to the properties.
Thanks a lot to both of you. So the following works as expected.
scala> Json.parse("""{"basic":"test"}""") \ "basic"
res137: play.api.libs.json.JsValue = "test"
I'd still like to understand what Json.toJson does. The docs state "Transform a stream of A to a stream of JsValue". Can anyone point out in what context this can be used?
I'm trying to check JsValue object in my Actor using play framework 2.2.2. When I try to use validate method, I receive exception not a result object:
try {
val result = data.validate[EventConfig]
Logger.debug("Result: "+result")
} catch {
case e =>
Logger.error("Exception: "+e)
}
Here is this exception:
Exception: play.api.libs.json.JsResultException: JsResultException(errors:List((,List(ValidationError(error.expected.jsnumber,WrappedArray())))))
Why is this happening, and how should I use validate method?
====== Update
I was using such Reads implementation:
implicit val EventConfig_reads = new Reads[EventConfig] {
def reads(json: JsValue): JsResult[EventConfig] = {
JsSuccess(new
EventConfig((json \ ConfigEventAttrs.PARAM).as[Int],
(json \ ConfigEventAttrs.PERIOD).as[Int],
(json \ ConfigEventAttrs.THRESHOLD).as[Int],
(json \ ConfigEventAttrs.TOGGLE).as[Boolean]))
}
}
The solution is to add catch clause:
implicit val EventConfig_reads = new Reads[EventConfig] {
def reads(json: JsValue): JsResult[EventConfig] = {
try {
JsSuccess(new
EventConfig((json \ ConfigEventAttrs.PARAM).as[Int],
(json \ ConfigEventAttrs.PERIOD).as[Int],
(json \ ConfigEventAttrs.THRESHOLD).as[Int],
(json \ ConfigEventAttrs.TOGGLE).as[Boolean]))
} catch {
case e: JsResultException =>
JsError(e.errors)
}
}
}
That is not the proper way to use validate. I don't think the documentation highlights it's importance as much as it should, but it's explained here, in the section called Using Validation.
data.validate[EventConfig] returns JsResult and not EventConfig. The preferred way to deal with errors is to fold the result:
data.validate[EventConfig].fold(
error => {
// There were validation errors, handle them here.
},
config => {
// `EventConfig` has validated, and is now in the scope as `config`, proceed as usual.
}
)
Let's examine this a bit. The signature if fold on a JsResult is as follows:
fold[X](invalid: (Seq[(JsPath, Seq[ValidationError])]) ⇒ X, valid: (A) ⇒ X): X
It accepts two functions as arguments that both return the same type of result. The first function is a Seq[(JsPath, Seq[ValidationError])]) => X. In my code above, error has the type Seq[(JsPath, Seq[ValidationError])]), which is essentially just a sequence of json paths tupled with their validation errors. Here you can dissect these errors and return the appropriate error messages accordingly, or do whatever else you may need to on failure.
The second function maps A => X, where A is the type JsResult has been validated as, in your case EventConfig. Here, you'll be able to handle your EventConfig type directly.
Causing and catching exceptions is not the way to handle this (and rarely is), as you will lose all of the accumulated validation errors.
Edit: Since the OP has updated his question with additional information regarding his defined Reads.
The problem with the Reads defined there is that they're using as[T]. When calling as, you're trying to force the given json path to type T, which will throw an exception if it cannot. So as soon as you reach the first validation error, an exception is thrown and you will lose all subsequent errors. Your use case is relatively simple though, so I think it would be better to adopt a more modern looking Reads.
import play.api.libs.json._
import play.api.libs.functional.syntax._
case class EventConfig(param: Int, period: Int, threshold: Int, toggle: Boolean)
object EventConfig {
implicit val jsonReads: Reads[EventConfig] = (
(__ \ ConfigEventAttrs.PARAM).read[Int] and
(__ \ ConfigEventAttrs.PERIOD).read[Int] and
(__ \ ConfigEventAttrs.THRESHOLD).read[Int] and
(__ \ ConfigEventAttrs.TOGGLE).read[Boolean]
)(EventConfig.apply _)
}
This is much more compact, and the use of the functional syntax will accumulate all of the validation errors into the JsResult, as opposed to throwing exceptions.
Edit 2: To address the OP's need for a different apply method.
If the parameters you're using the build an object from JSON differ from those of your case class, define a function to use for the JSON Reads instead of EventConfig.apply. Supposing your EventConfig is really like this in JSON:
(time: Long, param: Int)
But instead you want it to be like this:
case class EventConfig(time: Date, param: Int)
Define a function to create an EventConfig from the original parameters:
def buildConfig(time: Long, param: Int) = EventConfig(DateUtils.timeSecToDate(time), param)
Then use buildConfig instead of EventConfig.apply in your Reads:
implicit val jsonReads: Reads[EventConfig] = (
(__ \ "time").read[Long] and
(__ \ "param").read[Int]
)(buildConfig _)
I shortened this example, but buildConfig can be any function that returns EventConfig and parameters match those of the JSON object you're trying to validate.
Validating depends on your Reads method, and I've had an issue there. I should just catch this exception in my reads.