I wand to validate json that has a type value
I have a case class SearchRequestMessage with a companion object with json writer
Value T can be Long or UUID
case class SearchRequestMessage[T](jobId: UUID, offerId: UUID, search: T, searchByType: SearchByType)
object SearchRequestMessage{
implicit def searchResultsWrites[T: Writes]: Writes[SearchRequestMessage[T]] = new Writes[SearchRequestMessage[T]] {
def writes(searchSesult: SearchRequestMessage[T]) =
JsObject(
Seq(
"jobId" -> JsString(searchSesult.jobId.toString),
"offerId" -> JsString(searchSesult.offerId.toString),
"search" -> JsString(searchSesult.search.toString),
"searchByType" -> JsString(searchSesult.searchByType.toString)
))
}
}
Also I have SearchByTypes enum
object SearchByTypes extends Enumeration {
type SearchByType = Value
val Emails: SearchByType = Value(0, "EMAILS")
val Offer: SearchByType = Value(1, "OFFER")
implicit val SearchByTypeFormat: Format[SearchByTypes.Value] = JsonFormatter.enumFormat(SearchByTypes)
}
enum formatter
import play.api.libs.json._
object JsonFormatter {
def enumFormat[T <: Enumeration](enum: T): Format[T#Value] = new EnumFormatter[T](enum)
def enumWithIdsFormat[T <: Enumeration](enum: T): Format[T#Value] = new EnumWithIdsFormatter[T](enum)
class EnumFormatter[T <: Enumeration](enum: T) extends Format[T#Value] {
override def writes(o: T#Value): JsValue = o match {
case null => JsNull
case _ => JsString(o.toString)
}
override def reads(json: JsValue): JsResult[T#Value] = json match {
case JsString(value) => {
try {
JsSuccess(enum.withName(value))
} catch {
case _: NoSuchElementException =>
JsError(
s"Enumeration expected of type: '${enum.getClass}', but it does not appear to contain the value: '$value'")
}
}
case _ => JsError(s"Invalid JSON: $json. Error in '${enum.getClass}' field. Possible values: ${enum.values}")
}
}
class EnumWithIdsFormatter[T <: Enumeration](enum: T) extends Format[T#Value] {
private val nameField = "name"
private val idField = "id"
override def writes(o: T#Value): JsValue = JsObject(Map(idField -> JsNumber(o.id), nameField -> JsString(o.toString)))
override def reads(json: JsValue): JsResult[T#Value] = json match {
case JsObject(values) =>
values.get(idField) match {
case Some(JsNumber(value)) if value <= enum.maxId && value >= 0 ⇒
try {
JsSuccess(enum(value.toInt))
} catch {
case _: NoSuchElementException =>
JsError(
s"Enumeration expected of type: '${enum.getClass}', but it does not appear to contain the value: '$value'")
}
case Some(JsNumber(x)) ⇒
JsError(s"Invalid JSON:$json. Field '$idField': '$x' is out of range. Possible values:${enum.values.map(_.id)}")
case Some(_) ⇒
JsError(s"Invalid JSON:$json. Field '$idField' isn't number. Possible values:${enum.values.map(_.id)}")
case None ⇒
JsError(s"Invalid JSON:$json. Missing field '$idField'")
}
case _ =>
JsError(s"Invalid JSON: $json")
}
}
}
Now I want to validate json
import play.api.libs.json.Json
import play.api.libs.functional.syntax._
val obj = SearchRequestMessage(UUID.randomUUID(), UUID.randomUUID(), "email", SearchByTypes.Emails)
val json = Json.toJson(obj)
val result = ((json \ "jobId").validate[UUID] and
(json \ "offerId").validate[UUID] and
(json \ "search").validate[String] and
(json \ "searchByType").validate[SearchByType])(SearchRequestMessage[String].apply(_,_,_,_))
and an error appear:
Error:(41, 75) missing argument list for method apply in object SearchRequestMessage
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`.
(json \ "searchByType").validate[SearchByType])(SearchRequestMessage[String].apply(_,_,_,_))
How I can fix this error?
try
(json \ "searchByType").validate[SearchByType])(SearchRequestMessage[String].apply)
or (which is a bit verbose)
(json \ "searchByType").validate[SearchByType])((a,b,c,d) => SearchRequestMessage[String].apply(a,b,c,d))
This works
((json \ "jobId").validate[UUID] and
(json \ "offerId").validate[UUID] and
(json \ "search").validate[String] and
(json \ "searchByType").validate[SearchByType])((a: UUID, b: UUID, c: String, d: SearchByType) => SearchRequestMessage.apply[String](a,b,c,d))
Related
Here is my json:
{
"stringField" : "whatever",
"nestedObject": { "someProperty": "someValue"}
}
I want to map it to
case class MyClass(stringField: String, nestedObject:String)
nestedObject should not be deserialized, I want json4s to leave it as string.
resulting instance shouldBe:
val instance = MyClass(stringField="whatever", nestedObject= """ { "someProperty": "someValue"} """)
Don't understand how to do it in json4s.
You can define a custom serializer:
case object MyClassSerializer extends CustomSerializer[MyClass](f => ( {
case jsonObj =>
implicit val format = org.json4s.DefaultFormats
val stringField = (jsonObj \ "stringField").extract[String]
val nestedObject = compact(render(jsonObj \ "nestedObject"))
MyClass(stringField, nestedObject)
}, {
case myClass: MyClass =>
("stringField" -> myClass.stringField) ~
("nestedObject" -> myClass.nestedObject)
}
))
Then add it to the default formatter:
implicit val format = org.json4s.DefaultFormats + MyClassSerializer
println(parse(jsonString).extract[MyClass])
will output:
MyClass(whatever,{"someProperty":"someValue"})
Code run at Scastie
I'm having some trouble figuring out why compiler complains about not finding an implicit parameter for reads because I'm almost sure that it is in the scope. The error is the following:
Error:(13, 18) No Json deserializer found for type Config. Try to implement an implicit Reads or Format for this type.
test.validate[Config].map {
^
Error:(13, 18) not enough arguments for method validate: (implicit rds: play.api.libs.json.Reads[Config])play.api.libs.json.JsResult[wings.m2m.conf.model.Config].
Unspecified value parameter rds.
test.validate[Config].map {
^
and it happens in the following code:
import play.api.libs.json._
import play.api.libs.json.Reads._
import Config.JsonImplicits._
import scala.util.Try
object Test {
def main(args: Array[String]) {
val test = Json.obj("action" -> Config.Action.nameAcquisitionRequest.toString, "value" -> "hola")
test.validate[Config].map {
t => println(t)
t
}
}
}
/**
* Config companion object
*/
object Config {
type ValueType = String
val ActionKey = "action"
val ValueKey = "value"
object Action extends Enumeration {
type Action = Value
val nameAcquisitionRequest = Value("nameAcquisitionRequest")
val nameAcquisitionReject = Value("nameAcquisitionReject")
val nameAcquisitionAck = Value("nameAcquisitionAck")
val broadcast = Value("broadcast")
}
/**
* Json implicit conversions
*/
object JsonImplicits {
implicit object ConfigReads extends Reads[Config] {
def hTypeCast(action: Config.Action.Value, value: Config.ValueType): Config = {
action match {
case Config.Action.nameAcquisitionRequest => NameAcquisitionRequest(value)
case Config.Action.nameAcquisitionReject => NameAcquisitionReject(value)
case Config.Action.nameAcquisitionAck => NameAcquisitionAck(value)
}
}
override def reads(json: JsValue): JsResult[Config] = json match {
case json: JsObject =>
val action = (json \ ActionKey).as[String]
Try(Config.Action.withName(action)) map {
a =>
val value = (json \ ValueKey).as[String]
JsSuccess(hTypeCast(a, value))
} getOrElse (JsError("Can't convert to Config"))
case _ => JsError("Can't convert to Config")
}
}
implicit object ConfigWrites extends OWrites[Config] {
def jsObjectCreator(action: Config.Action.Value, value: Config.ValueType): JsObject = {
Json.obj(ActionKey -> action.toString, ValueKey -> Json.toJson(value))
}
override def writes(o: Config): JsObject = o match {
case c: NameAcquisitionRequest => jsObjectCreator(Config.Action.nameAcquisitionRequest, c.value)
case c: NameAcquisitionReject => jsObjectCreator(Config.Action.nameAcquisitionReject, c.value)
case c: NameAcquisitionAck => jsObjectCreator(Config.Action.nameAcquisitionAck, c.value)
}
}
}
}
sealed trait Config {
val value: Config.ValueType
}
/**
* Intermediate config message
* #param value
*/
case class NameAcquisitionRequest(override val value: String)
extends Config
case class NameAcquisitionReject(override val value: String)
extends Config
case class NameAcquisitionAck(override val value: String)
extends Config
case class Broadcast(override val value: String)
extends Config
the error occurs when executing the main method on the Test object. To make this example work, make sure to add the following dependency in the SBT: "com.typesafe.play" %% "play-json" % "2.4.1" . And I'm not sure, but maybe this resolver is needed: resolvers += "Typesafe Repo" at "http://repo.typesafe.com/typesafe/releases/"
I am not sure what you are trying to achieve and whether this solves your problem but here you go:
test.validate[Config](Config.JsonImplicits.ConfigReads).map {
t => println(t)
t
}
I have the following Json string:
{
"references": {
"configuratorId": "conf id",
"seekId": "seekid",
"hsId": "hsid",
"fpId": "fpid"
}
}
And i want to create an object from it in my Rest API Controller.
The code:
case class References(configuratorId: Option[String], seekId: Option[String], hsId: Option[String], fpId: Option[String]) {}
The Formatter:
trait ProductFormats extends ErrorFormats {
implicit val referencesFormat = Json.format[References]
implicit val referenceFormat = new Format[References]{
def writes(item: References):JsValue = {
Json.obj(
"configuratorId" -> item.configuratorId,
"seekId" -> item.seekId,
"hsId" -> item.hsId,
"fpId" -> item.fpId
)
}
def reads(json: JsValue): JsResult[References] =
JsSuccess(new References(
(json \ "configuratorId").as[Option[String]],
(json \ "seekId").as[Option[String]],
(json \ "hsId").as[Option[String]],
(json \ "fpId").as[Option[String]]
))
}
Code in my controller:
def addProducts(lang: String, t: String) = Action {
request =>
request.body.asJson.map {
json =>
val req = json.as[References]
println(req.configuratorId.getOrElse("it was empty !!"))
Ok(Json.toJson((req)))
}.getOrElse {
println("Bad json:" + request.body.asText.getOrElse("No data in body"))
BadRequest("Incorrect json data")
}
}
The object is full with null values.. I guess my reads is wrong - but i cant figure out why.
Thanks!!
You should change the code in your controller to:
val req = (json \ "references").as[References]
object Users {
implicit object UserReads extends Reads[User] {
def reads(json: JsValue) = JsSuccess(User(
Id((json \ "id").as[String].toLong),
(json \ "name").as[String],
(json \ "email").as[String]
}
implicit object UserWrites extends Writes[User] {
def writes(user: User) = JsObject(Seq(
"id" -> JsNumber(user.id.get),
"name" -> JsString(user.name),
"email" -> Json.toJson(user.email)
}
def view(id: Long) = Action { implicit request =>
Ok(Json.toJson(User.find(id)))
}
def all() = Action { implicit request =>
Ok(Json.toJson(User.findAll()))
}
def save = Action(parse.json) { request =>
val userJson = request.body
val user = userJson.as[User]
try {
User.create(user)
Ok("Saved")
} catch {
case e: IllegalArgumentException => BadRequest("Error")
}
}
}
case class User(
id: Pk[Long] = NotAssigned,
name: String = "",
email: String = "")
The above two are my controller & model, when I send the User data [id, name, email] as json using angular JS, it creates the User object in the database. But it should be able to create when I input only [name, email] or just [name] as email could be null. If I'm not wrong I should adjust these in the reads and writes method of User, is it?.
Also, can we have two reads/writes for different pursposes, if so how can that be achieved - throw some light. Thanks.
One Issue fixed with the following:
case class User(
id: Pk[Long] = NotAssigned,
name: String = "",
email: Option[String])
implicit object UserReads extends Reads[User] {
def reads(json: JsValue) = JsSuccess(User(
Id((json \ "id").as[String].toLong),
(json \ "name").as[String],
(json \ "email").asOpt[String])
}
implicit object UserWrites extends Writes[User] {
def writes(user: User) = JsObject(Seq(
"id" -> JsNumber(user.id.get),
"name" -> JsString(user.name),
"email" -> Json.toJson(user.email))
}
Now email can be null, but what about id - PK[Long]
I did not compile this, but it should work:
case class User(
id: Pk[Long] = NotAssigned,
name: String = "",
email: Option[String])
implicit object UserReads extends Reads[User] {
def reads(json: JsValue) = JsSuccess(User(
(json \ "id").asOpt[Long].map(id => Id[Long](id)).getOrElse(NotAssigned)
(json \ "name").as[String],
(json \ "email").asOpt[String])
}
implicit object UserWrites extends Writes[User] {
def writes(user: User) = JsObject(Seq(
"id" -> JsNumber(user.id.get),
"name" -> JsString(user.name),
"email" -> Json.toJson(user.email))
}
Basically you take the id as Option[Long] like you did with the email. Then you check if it is set and if yes you create the Pk instance with Id[Long](id) and if not you provide the NotAssigned Pk singleton instance.
Additional Tip
Alternatively you can try to use
implicit val jsonFormatter = Json.format[User]
It will create the Reads and the Writes for you directly at compile time.
There are two problem with this if you are using an anorm object directly:
First you need a Format for the Pk[Long]
implicit object PkLongFormat extends Format[Pk[Long]] {
def reads(json: JsValue): JsResult[Pk[Long]] = {
json.asOpt[Long].map {
id => JsSuccess(Id[Long](id))
}.getOrElse(JsSuccess(NotAssigned))
}
def writes(id: Pk[Long]): JsNumber = JsNumber(id.get)
}
Second it does not work, if you do not send an id at all, not even with value null, so your client needs to send {"id": null, "name": "Tim"} because it does not even try to call the PkFormatter which could handle the JsUndefined, but simply gives you an "validate.error.missing-path" error
If you don't want to send null ids you cannot use the macro Json.format[User]
Type anorm.Pk is almost exactly like scala.Option in form and function. Avoid writing concrete Reads and Writes for all types it might possibly contain. An example that follows the OptionReads and OptionWrites implementations is as follows:
implicit def PkWrites[T](implicit w: Writes[T]): Writes[Pk[T]] = new Writes[Pk[T]] {
def writes(o: Pk[T]) = o match {
case Id(value) => w.writes(value)
case NotAssigned => JsNull
}
}
implicit def PkReads[T](implicit r: Reads[T]): Reads[Pk[T]] = new Reads[Pk[T]] {
def reads(js: JsValue): JsResult[Pk[T]] =
r.reads(js).fold(e => JsSuccess(NotAssigned), v => JsSuccess(Id(v)))
}
This way, you support every T for which there's a respective Reads[T] or Writes[T], and it's more reliable in handling Id and NotAssigned.
I'm trying to deserialize a JsArray into a List[T] in a playframework application using Scala. After some research I found this method which is supposed to do the needed work:
/**
* Deserializer for List[T] types.
*/
implicit def listReads[T](implicit fmt: Reads[T]): Reads[List[T]] = new Reads[List[T]] {
def reads(json: JsValue) = json match {
case JsArray(ts) => ts.map(t => fromJson(t)(fmt)).toList
case _ => throw new RuntimeException("List expected")
}
}
The problem is that I didn't know how to use it. Any help is welcome.
Here's a quick example:
scala> import play.api.libs.json._
import play.api.libs.json._
scala> Json.toJson(List(1, 2, 3)).as[List[Int]]
res0: List[Int] = List(1, 2, 3)
And if you have a custom type with a Format instance:
case class Foo(i: Int, x: String)
implicit object fooFormat extends Format[Foo] {
def reads(json: JsValue) = Foo(
(json \ "i").as[Int],
(json \ "x").as[String]
)
def writes(foo: Foo) = JsObject(Seq(
"i" -> JsNumber(foo.i),
"x" -> JsString(foo.x)
))
}
It still works:
scala> val foos = Foo(1, "a") :: Foo(2, "bb") :: Nil
foos: List[Foo] = List(Foo(1,a), Foo(2,bb))
scala> val json = Json.toJson(foos)
json: play.api.libs.json.JsValue = [{"i":1,"x":"a"},{"i":2,"x":"bb"}]
scala> json.as[List[Foo]]
res1: List[Foo] = List(Foo(1,a), Foo(2,bb))
This approach would also work if your custom type had a xs: List[String] member, for example: you'd just use (json \ "xs").as[List[String]] in your reads method and Json.toJson(foo.xs) in your writes.