I want to recursively write a class to Json, so I'm using the following implicit writes:
implicit val writesObject : Writes[Object] = (
(__ \ "id").writeNullable[String] ~
(__ \ "list").lazyWriteNullable(Writes.traversableWrites[Object](writesObject))
)(unlift(Object.unapply)
where Object is a class like this:
case class Object(id: Option[String], list: Option[Seq[Object]])
It works, however I would like to prevent it from printing anything if "list" is empty. For example:
I want:
{ id: "someID",
list: [
{
id: "someOtherId"
}
]
}
I currently get(but don't want):
{ id: "someID",
list: [
{
id: "someOtherId"
list: []
}
]
}
How can I achieve this? I'm new to Play/Scala and not sure exactly what should I be looking at so any pointers would be helpful. I'm using Scala 2.2.1.
PS: I've checked Scala Json Combinators but didn't see any reference on how to get this done.
Update:
So my issue is not that list is null, but that list is empty. That's why lazyWriteNullable wasn't working.
Testing johanandren answer I came up with the following extension to JsPath that returns Option[T] and supports the lazy format for recursive writes:
def lazyWriteNullableIterable[T <: Iterable[_]](w: => Writes[T]): OWrites[Option[T]] = OWrites((t: Option[T]) => {
if(t != null) {
t.getOrElse(Seq.empty).size match {
case 0 => Json.obj()
case _ => Writes.nullable[T](path)(w).writes(t)
}
}
else {
Json.obj()
}
})
Thanks
You can create a custom OFormat that will do this. By implicitly decorating JsPath with it you can include it in your json combinator definitions:
implicit class PathAdditions(path: JsPath) {
def readNullableIterable[A <: Iterable[_]](implicit reads: Reads[A]): Reads[A] =
Reads((json: JsValue) => path.applyTillLast(json).fold(
error => error,
result => result.fold(
invalid = (_) => reads.reads(JsArray()),
valid = {
case JsNull => reads.reads(JsArray())
case js => reads.reads(js).repath(path)
})
))
def writeNullableIterable[A <: Iterable[_]](implicit writes: Writes[A]): OWrites[A] =
OWrites[A]{ (a: A) =>
if (a.isEmpty) Json.obj()
else JsPath.createObj(path -> writes.writes(a))
}
/** When writing it ignores the property when the collection is empty,
* when reading undefined and empty jsarray becomes an empty collection */
def formatNullableIterable[A <: Iterable[_]](implicit format: Format[A]): OFormat[A] =
OFormat[A](r = readNullableIterable(format), w = writeNullableIterable(format))
}
This would allow you to create formats/reads/writes using the json combinator syntax like this:
case class Something(as: List[String], v: String)
import somewhere.PathAdditions
val reads: Reads[Something] = (
(__ \ "possiblyMissing").readNullableIterable[List[String]] and
(__ \ "somethingElse").read[String]
)(Something)
val writes: Writes[Something] = (
(__ \ "possiblyMissing").writeNullableIterable[List[String]] and
(__ \ "somethingElse").write[String]
)(unlift(Something.unapply))
val format: Format[Something] = (
(__ \ "possiblyMissing").formatNullableIterable[List[String]] and
(__ \ "somethingElse").format[String]
)(Something, unlift(Something.unapply))
Related
According to this documentation (official):
https://www.playframework.com/documentation/2.8.x/ScalaJsonCombinators
I have to create a case class, after that I have to create a JsonReader:
val nameReads: Reads[String] = (JsPath \ "name").read[String]
then
val nameResult: JsResult[String] = json.validate[String](nameReads)
So, the result would be into nameResult and it was expecting that data was accessible like this:
println(nameResult.name)
Unfortunately, it doesn't work. It doesn't print results or return them.
First of all I work with Future and read JSON from web
implicit val context = scala.concurrent.ExecutionContext.Implicits.global
val userReads: Reads[User] = (
(JsPath \ "id").read[Int] and
(JsPath \ "login").read[String]
)
val futureResult = wc.url(path).get().map {
response =>
response.json.validate[User](userReads)
}
futureResult.map(r => println(r.id, r.login))
But! This code works, but it isn't in documentation.
implicit val context = scala.concurrent.ExecutionContext.Implicits.global
val userReads: Reads[User] = (
(JsPath \ "id").read[Int] and
(JsPath \ "login").read[String]
)
val futureResult = wc.url(path).get().map {
response =>
UserTest(
(response.json \ "id").as[String],
(response.json \ "login").as[String]
)
}
futureResult.map(r => println(r.id, r.login))
Does somebody know why code into documentation doesn't work? What is wrong with it?
Could I use my code?
Calling validate[User] doesn't return a User but a JsResult[User]. This is because the JSON data might be invalid and your code needs to handle this case. There is an example in the documentation that you have linked to:
json.validate[Place] match {
case JsSuccess(place, _) => {
val _: Place = place
// do something with place
}
case e: JsError => {
// error handling flow
}
}
I am using play JSON library to validate a JSON schema.
I have the following JSON data. And I need to validate a schema to this JSON.
// Scala Code
package com.base
import scala.io.Source
import play.api.libs.json._
import play.api.libs.json.JsNull
import play.api.libs.json.Reads._
import play.api.libs.functional.syntax._
import com.utils.ReadJsonFile._
case class JsValueToModel(web_pages: Array[String], name: String, alpha_two_code: String , state_province: Option[String], domains: Array[String], country: String)
object ValidateJSON extends App {
// Parsing Json file to JsArray
val json = Json.parse(readJson1)
// Using Recursive path by each key to traverse json
val web_pages = json \\ "web_pages"
val name = json \\ "name"
val alpha_two_code = json \\ "alpha_two_code"
val state_province = json \\ "state-province"
val domains = json \\ "domains"
val country = json \\ "country"
// Converting JsValue to Model to read the json key value
implicit val JsValueToModelReads: Reads[JsValueToModel] = (
(JsPath \ "web_pages").read[Array[String]] and
(JsPath \ "name").read[String] and
(JsPath \ "alpha_two_code").read[String] and
(JsPath \ "state-province").readNullable[String] and
(JsPath \ "domains").read[Array[String]] and
(JsPath \ "country").read[String]
)(JsValueToModel.apply _)
// Validation Json Object with JsonSchemaModel
val validateJson = json.validate[List[JsValueToModel]] match {
case s: JsSuccess[List[JsValueToModel]] => {
// val v: List[JsValueToModel] = s.get
println("Validation Success")
}
case e: JsError => {
println("Validation Errors: " + JsError.toJson(e).toString)
}
}
// Length of json
println(json.as[JsArray].value.size)
I am getting following error -
json.as[JsObject].map((a,b) => (a == null))
value map is not a member of play.api.libs.json.JsObject
[error] json.as[JsObject].map((a,b) => (a == null))
json.map((a,b) => (a == null))
value map is not a member of play.api.libs.json.JsValue
[error] json.map((a,b) => (a == null))
I am not sure what to change. I tried to do some pattern matching but not able to do - something like -
def findAndCountNull(a: JsValue):(String,Option[Any]) = a match {
case (x,y) => (x == null) // ???
case _ => a
}
Thanks to getting some help regarding this.
I saw some other libraries like json4s, JsZipper as well as from lift but want to use play json library to understand.
Use JSON automated mapping:
import play.api.libs.json.{JsError, JsSuccess, Json}
import scala.io.{Codec, Source}
object Data {
implicit val jsonFormat = Json.format[Data]
}
case class Data(web_pages: Seq[String], name: String, alpha_two_code: String, `state-province`: Option[String], domains: Seq[String], country: String)
val str = Source.fromURL("https://raw.githubusercontent.com/Hipo/university-domains-list/master/world_universities_and_domains.json")(Codec.UTF8).mkString
Json.parse(str).validate[Seq[Data]] match {
case JsSuccess(x, _) =>
Right(x)
case JsError(errors) =>
Left(errors)
}
Result:
res0: scala.util.Either[Seq[(play.api.libs.json.JsPath, Seq[play.api.libs.json.JsonValidationError])],Seq[Data]] =
Right(List(Data(List(https://www.cstj.qc.ca, https://ccmt.cstj.qc.ca, https://ccml.cstj.qc.ca),Cégep de Saint-Jérôme,CA,None,List(cstj.qc.ca),Canada), Data(List(http://www.lindenwood.edu/),Lindenwood University,US,None,List(lindenwood.edu),United States), Data(List(http://www.davietjal.org/),DAV Institute of Engineering & Technology,IN,Some(Punjab),List(davietjal.org),India), Data(List(http://www.lpu.in/),Lovely Professional University,IN,Some(Punjab),List(lpu.in),India), Data(List(https://sullivan.edu/),Sullivan University,US,None,List(sullivan.edu),United States), Data(List(https://www.fscj.edu/),Florida State College at Jacksonville,US,None,List...
For just counting null - use pattern matching (simplified - you should check JSArray too):
val parsed = Json.parse(str)
val seq = parsed.as[JsArray].value.map(_ \\ "state-province").map(x => x.head)
val nulls = seq.count {
case JsNull => true
case _ => false
}
println(s"$nulls")
I am trying to pass in a POST request to a REST API developped with Play! (2.5) an object that I would like to use a Scala Graph (from the graph-core dependency).
It looks like the graph already has JSON serialization/deserialization methods based on lift-json, but I am not sure how to "plug" that into Play Json library. Until now I was using implicit converters (with Reads/Writes methods) but I would like to avoid having to write my own methods for the graph part since it is already part of the library itself.
For instance, let's say I have this code:
import java.util.UUID
import scalax.collection.Graph
case class Task(
id: UUID,
status: String)
case class Stuff(
id: UUID = UUID.randomUUID(),
name: String,
tasks: Option[Graph[Task, DiEdge]])
implicit val stuffWrites: Writes[Stuff] = (
(JsPath \ "id").write[UUID] and
(JsPath \ "name").write[String]
)(unlift(Stuff.unapply))
implicit val stuffReads: Reads[Stuff] = (
(JsPath \ "id").read[UUID] and
(JsPath \ "name").read[String]
)(Stuff.apply _)
implicit val taskWrite: Writes[Task] = (
(JsPath \ "id").write[UUID] and
(JsPath \ "status").write[String]
)(unlift(Task.unapply))
implicit val taskReads: Reads[Task] = (
(JsPath \ "id").read[UUID] and
(JsPath \ "status").read[String]
)(Task.apply _)
I miss the part to serialize the graph and the parenting. Should I rewrite everything from scratch, or can I rely on methods toJson/fromJson from scalax.collection.io.json ?
Since I struggled a bit to get this working, I thought I would share the code:
class UUIDSerializer extends Serializer[UUID] {
private val UUIDClass = classOf[UUID]
def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), UUID] = {
case (TypeInfo(UUIDClass, _), json) => json match {
case JString(id) => UUID.fromString(id)
case x => throw new MappingException("Can't convert " + x + " to UUID")
}
}
def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
case x: UUID => JString(x.toString)
}
}
val extraSerializers = new UUIDSerializer :: Nil
implicit val formats = Serialization.formats(NoTypeHints) ++ extraSerializers
val taskDescriptor = new NodeDescriptor[Task](typeId = "Tasks", customSerializers=extraSerializers) {
def id(node: Any) = node match {
case Task(id, _) => id.toString
}
}
val quickJson = new Descriptor[Task](
defaultNodeDescriptor = taskDescriptor,
defaultEdgeDescriptor = Di.descriptor[Task]()
)
implicit val tasksWrites = new Writes[Graph[Task, DiEdge]] {
def writes(graph: Graph[Task, DiEdge]): JsValue = {
val json = graph.toJson(quickJson)
Json.parse(json.toString)
}
}
implicit val tasksReads = new Reads[Graph[Task, DiEdge]] {
def reads(json: JsValue): JsResult[Graph[Task, DiEdge]] = {
try {
val graph = Graph.fromJson[Task, DiEdge](json.toString, quickJson)
JsSuccess(graph)
}
catch {
case e: Exception =>
JsError(e.toString)
}
}
}
implicit def stuffModelFormat = Jsonx.formatCaseClass[Stuff]
You can try writing companion objects for yours case classes where you specify the formatting.
Example:
object Task {
implicit val taskModelFormat = Json.format[Task]
}
object Stuff {
implicit val staffModelFormat = Json.format[Stuff]
}
instead of the above implicits. With this solution compiler will resolve the known formatters for you and you could be only required to specify the missing/unknown types instead of the whole structure.
I have a scenario that when parsing a json to a case class like this
implicit val userRead: Reads[User] = (
(__ \ "name").read[String] ~
(__ \ "email").readNullable[String] ~
(__ \ "phone").readNullable[String] ~
Reads.pure(None)
)(User.apply _)
I don't require both email and phone to be available, but at least one of them have to be available.
In my case class definition, I can prevent the case that both of them are empty with
case class User(name: String, email: Option[String], phone: Option[String], id: Option[Long] = None) {
require(email.nonEmpty || phone.nonEmpty, "must have at least an email or phone number")
}
However, doing this will generate an exception, and a 500 status is responded, when this should be a 400 error due to user input.
I can of course manually perform the verification in my controller, but I wonder if there is a cleaner way to do this.
I can only suggest writing you own "reads" manually
case class A(i: Option[Int], j: Option[Int])
implicit val reads: Reads[A] = new Reads[A] {
override def reads(json: JsValue): JsResult[A] = {
(for {
i <- (json \ "i").validateOpt[Int]
j <- (json \ "j").validateOpt[Int]
} yield A(i, j))
.filter(JsError("both values can't be empty"))(a ⇒ a.j.nonEmpty || a.i.nonEmpty)
}
}
And to test:
scala> Json.parse("""{ "i" : 1} """).validate[A]
res4: play.api.libs.json.JsResult[A] = JsSuccess(A(Some(1),None),)
scala> Json.parse("""{} """).validate[A]
res5: play.api.libs.json.JsResult[A] = JsError(List((,List(ValidationError(List(from and to in range can't be both empty),WrappedArray())))))
You can achieve this with just a small addition to your existing code (.filter(ValidationError("must have at least an email or phone number"))(u => u.email.isDefined || u.phone.isDefined)):
case class User(name: String, email: Option[String], phone: Option[String], id: Option[Long] = None)
implicit val userRead: Reads[User] = (
(__ \ "name").read[String] ~
(__ \ "email").readNullable[String] ~
(__ \ "phone").readNullable[String] ~
Reads.pure(None)
)(User.apply _).filter(ValidationError("must have at least an email or phone number"))(u => u.email.isDefined || u.phone.isDefined)
Now, to show that this works:
scala> Json.parse("""{ "name" : "foobar"} """).validate[User]
res3: play.api.libs.json.JsResult[User] = JsError(List((,List(ValidationError(List(must have at least an email or phone number),WrappedArray())))))
scala> Json.parse("""{ "name" : "foobar", "email":"test"} """).validate[User]
res4: play.api.libs.json.JsResult[User] = JsSuccess(User(foobar,Some(test),None,None),)
scala> Json.parse("""{ "name" : "foobar", "phone":"test"} """).validate[User]
res5: play.api.libs.json.JsResult[User] = JsSuccess(User(foobar,None,Some(test),None),)
I am following play-salat (github.com/leon/play-salat) to create a model for a json input and save to mongodb. How can I create the implicit json read for List collection which might be missing in the input json? The following code gives me the validation error if the 'positions' is missing from input json.
case class LIProfile(
id: ObjectId = new ObjectId,
positions: List[Position] = Nil
)
object LIProfile extends LIProfileDAO with LIProfileJson
trait LIProfileDAO extends ModelCompanion[LIProfile, ObjectId] {
def collection = mongoCollection("liprofiles")
val dao = new SalatDAO[LIProfile, ObjectId](collection) {}
// Indexes
collection.ensureIndex(DBObject("emailAddress" -> 1), "li_profile_email", unique = true)
// Queries
def findOneByEmail(email: String): Option[LIProfile] = dao.findOne(MongoDBObject("emailAddress" -> email))
}
trait LIProfileJson {
implicit val liprofileJsonWrite = new Writes[LIProfile] {
def writes(p: LIProfile): JsValue = {
Json.obj(
"id" -> p.id,
"positions" -> p.positions
)
}
}
implicit val liprofileJsonRead = (
(__ \ 'id).read[ObjectId] ~
(__ \ 'positions).read (
(__ \ 'values).read[List[Position]]
)
)(LIProfile.apply _)
}
Use readNullable in order to retrieve an Option and map that to the contained list or the empty list.
implicit val liprofileJsonRead = (
(__ \ 'id).read[ObjectId] ~
(__ \ 'positions).readNullable (
(__ \ 'values).read[List[Position]]
).map {
case Some(l) => l
case None => Nil
}
)(LIProfile)
or even shorter:
implicit val liprofileJsonRead = (
(__ \ 'id).read[ObjectId] ~
(__ \ 'positions).readNullable (
(__ \ 'values).read[List[Position]]
).map { l => l.getOrElse(Nil) }
)(LIProfile)
I'm not quite sure what imports you really need here, my code compiles using:
import play.api.libs.json._
import play.api.libs.json.Reads._
import play.api.libs.functional.syntax._