I have a big case class with 10+ fields, that represents a JSON payload, that comes from user. Most of fields are optional, so I'm using Option in this cases (Option[String] for string fields. And that is nice approach, until I need an optional sequence. I think that writing Option[Seq[String]] is weird, cause empty sequence is enough to show that there is no data (for any reason).
I could handle it in manual way, though:
implicit val reads = new Reads[MyCaseClass] {
def reads(js: JsValue): JsResult[MyCaseClass] = {
JsSuccess(MyCaseClass(
(js \ "unit_code").as[String],
// other fields omited
(js \ "positions").asOpt[Seq[String]] match {
case Some(seq: Seq[String]) => seq
case None => Seq.empty[String]
}
))
}
}
But I don't won't to write all this stuff manually. There could be mistakes, I need to test it separately, and, for sure, it takes much more time, rather than to write implicit val f = Json.format[MyCaseClass].
Is there any option to separately handle only one field, that other fields handling let on the default macro?
Thanks for user 'cchantep' to pointing to Json transformers.
So that is how I solve the issue:
Case class:
case class MyCaseClass(unit_code: String, positions: Seq[String] = Seq.empty)
Companion object
object MyCaseClass {
private val readsTransformer: Reads[JsObject] = __.json.update(
__.read[JsObject]
.map{ o =>
if (o.keys.exists(p => p.equals("positions"))) {
o
} else {
o ++ Json.obj("positions" -> JsArray())
}
}
)
implicit val readsImplicit: Reads[MyCaseClass] = readsTransformer.andThen(Json.reads[MyCaseClass])
implicit val writesImplicit: OWrites[MyCaseClass] = Json.writes[MyCaseClass]
}
That looks a bit cumbersome, but it is possible to write common function that creates transformer for exact field, so in each companion object it wouldn't be so verbose.
Related
I have a case class that's a "Bag of Fields" - lots of fields that represent a business entity. For my use case, the distinction between "", null, and the k/v being totally absent from the JSON object is irrelevant to me. In this case class these fields are already Option[String]s, so I'd like all 3 of those cases to collapse into None. I believe the current behavior of the auto-generated Reads is what I want except for the empty string case.
This BOF is likely to change in the future, so "just implement your own Reads" (as suggested here: make play-json read the empty string as None for a type of Option[T]) or something else where I have to re-enumerate all the fields is a non-starter.
I think what I may need is Play's 'Json Transformers'. It appears trivial to write a transformer that removes entries if they have empty values, but what I cant figure out is how to compose it with the auto-generated Reads implementation.
I imagine what I need is some combinator over Reads and transformers, some signature like: (JSON -> JSON, JSON -> T) -> (JSON -> T). Obviously I've found this page: https://www.playframework.com/documentation/2.5.x/ScalaJsonCombinators, but none of the listed combinators does what I want I believe. Could this combinator be easily implemented? I'd be a little out of my type-fu depth but that would be a great solution if I could get some pointers.
Here's what worked for me:
class RemoveEmpty[T] (reader: Reads[T]) extends Reads[T] {
override def reads(json: JsValue): JsResult[T] = json match {
case JsObject(underlying) => {
reader.reads(JsObject(underlying.filterNot{ case (k, v) => jsonValueEmpty(v) } ))
}
case _ => {
JsError("Non-JsObj passed to RemoveEmpty")
}
}
def jsonValueEmpty(v: JsValue) = v match {
case JsNull | JsString("") => true
case _ => false
}
}
Then you can just use it like this:
implicit val myTypeReads = new RemoveEmpty(Json.reads[MyType])
For the validate method on request.body it matches the attribute name and value type of the json object to those defined in the model definition. Now if I were to add an extra attribute to the json object and try to validate it, it passes as a JsSuccess when it shouldn't.
{
"Name": "Bob",
"Age": 20,
"Random_Field_Not_Defined_in_Models": "Test"
}
My Person Class is defined as follows
case class Person(name: String, age: Int)
I'm assuming you've been using the built-in Reads[T] or Format[T] converters that Play gives you via Json.reads[T], e.g.:
import play.api.libs.json._
val standardReads = Json.reads[Person]
While these are super-handy, if you need additional validation, you'll have to define a custom Reads[Person] class; but fortunately we can still leverage the built-in JSON-to-case-class macro to do the basic checking and conversion, and then add an extra layer of custom checks if things seem OK:
val standardReads = Json.reads[Person]
val strictReads = new Reads[Person] {
val expectedKeys = Set("name", "age")
def reads(jsv:JsValue):JsResult[Person] = {
standardReads.reads(jsv).flatMap { person =>
checkUnwantedKeys(jsv, person)
}
}
private def checkUnwantedKeys(jsv:JsValue, p:Person):JsResult[Person] = {
val obj = jsv.asInstanceOf[JsObject]
val keys = obj.keys
val unwanted = keys.diff(expectedKeys)
if (unwanted.isEmpty) {
JsSuccess(p)
} else {
JsError(s"Keys: ${unwanted.mkString(",")} found in the incoming JSON")
}
}
}
Note how we utilize standardReads first, to make sure we're dealing with something that can be converted to a Person. No need to reinvent the wheel here.
We use flatMap to effectively short-circuit the conversion if we get a JsError from standardReads - i.e. we only call checkUnwantedKeys if needed.
checkUnwantedKeys just uses the fact that a JsObject is really just a wrapper around a Map, so we can easily check the names of the keys against a whitelist.
Note that you could also write that flatMap using a for-comprehension, which starts to look a lot cleaner if you need even more checking stages:
for {
p <- standardReads.reads(jsv)
r1 <- checkUnexpectedFields(jsv, p)
r2 <- checkSomeOtherStuff(jsv, r1)
r3 <- checkEvenMoreStuff(jsv, r2)
} yield r3
If you want to avoid too much boilerplate it is possible to make a more generic solution using a little bit of scala reflection:
import play.api.libs.json._
import scala.reflect.runtime.universe._
def checkedReads[T](underlyingReads: Reads[T])(implicit typeTag: TypeTag[T]): Reads[T] = new Reads[T] {
def classFields[T: TypeTag]: Set[String] = typeOf[T].members.collect {
case m: MethodSymbol if m.isCaseAccessor => m.name.decodedName.toString
}.toSet
def reads(json: JsValue): JsResult[T] = {
val caseClassFields = classFields[T]
json match {
case JsObject(fields) if (fields.keySet -- caseClassFields).nonEmpty =>
JsError(s"Unexpected fields provided: ${(fields.keySet -- caseClassFields).mkString(", ")}")
case _ => underlyingReads.reads(json)
}
}
}
Then you can specify your reads instances as:
implicit val reads = checkedReads(Json.reads[Person])
This leverages a fair bit of Scala type magic and also the reflection library (that lets you look at fields on classes).
Rather than relying on a fixed set of fields the classFields method gets all of the fields dynamically for the case class (type param T). It looks at all of the members and collects only the case class accessors (otherwise we'd pick up methods like toString). It returns a Set[String] of field names.
You'll notice that the checkedReads takes an implicit TypeTag[T]. This is supplied by the compiler at compile time and used by the typeOf method.
The remaining code is fairly self explanatory. If the incoming json matches our first case (it is a JsObject and there are fields not on the case class) then we return a JsError. Otherwise we pass it on to the underlying reader.
For the validate method on request.body it matches the attribute name and value type of the json object to those defined in the model definition. Now if I were to add an extra attribute to the json object and try to validate it, it passes as a JsSuccess when it shouldn't.
{
"Name": "Bob",
"Age": 20,
"Random_Field_Not_Defined_in_Models": "Test"
}
My Person Class is defined as follows
case class Person(name: String, age: Int)
I'm assuming you've been using the built-in Reads[T] or Format[T] converters that Play gives you via Json.reads[T], e.g.:
import play.api.libs.json._
val standardReads = Json.reads[Person]
While these are super-handy, if you need additional validation, you'll have to define a custom Reads[Person] class; but fortunately we can still leverage the built-in JSON-to-case-class macro to do the basic checking and conversion, and then add an extra layer of custom checks if things seem OK:
val standardReads = Json.reads[Person]
val strictReads = new Reads[Person] {
val expectedKeys = Set("name", "age")
def reads(jsv:JsValue):JsResult[Person] = {
standardReads.reads(jsv).flatMap { person =>
checkUnwantedKeys(jsv, person)
}
}
private def checkUnwantedKeys(jsv:JsValue, p:Person):JsResult[Person] = {
val obj = jsv.asInstanceOf[JsObject]
val keys = obj.keys
val unwanted = keys.diff(expectedKeys)
if (unwanted.isEmpty) {
JsSuccess(p)
} else {
JsError(s"Keys: ${unwanted.mkString(",")} found in the incoming JSON")
}
}
}
Note how we utilize standardReads first, to make sure we're dealing with something that can be converted to a Person. No need to reinvent the wheel here.
We use flatMap to effectively short-circuit the conversion if we get a JsError from standardReads - i.e. we only call checkUnwantedKeys if needed.
checkUnwantedKeys just uses the fact that a JsObject is really just a wrapper around a Map, so we can easily check the names of the keys against a whitelist.
Note that you could also write that flatMap using a for-comprehension, which starts to look a lot cleaner if you need even more checking stages:
for {
p <- standardReads.reads(jsv)
r1 <- checkUnexpectedFields(jsv, p)
r2 <- checkSomeOtherStuff(jsv, r1)
r3 <- checkEvenMoreStuff(jsv, r2)
} yield r3
If you want to avoid too much boilerplate it is possible to make a more generic solution using a little bit of scala reflection:
import play.api.libs.json._
import scala.reflect.runtime.universe._
def checkedReads[T](underlyingReads: Reads[T])(implicit typeTag: TypeTag[T]): Reads[T] = new Reads[T] {
def classFields[T: TypeTag]: Set[String] = typeOf[T].members.collect {
case m: MethodSymbol if m.isCaseAccessor => m.name.decodedName.toString
}.toSet
def reads(json: JsValue): JsResult[T] = {
val caseClassFields = classFields[T]
json match {
case JsObject(fields) if (fields.keySet -- caseClassFields).nonEmpty =>
JsError(s"Unexpected fields provided: ${(fields.keySet -- caseClassFields).mkString(", ")}")
case _ => underlyingReads.reads(json)
}
}
}
Then you can specify your reads instances as:
implicit val reads = checkedReads(Json.reads[Person])
This leverages a fair bit of Scala type magic and also the reflection library (that lets you look at fields on classes).
Rather than relying on a fixed set of fields the classFields method gets all of the fields dynamically for the case class (type param T). It looks at all of the members and collects only the case class accessors (otherwise we'd pick up methods like toString). It returns a Set[String] of field names.
You'll notice that the checkedReads takes an implicit TypeTag[T]. This is supplied by the compiler at compile time and used by the typeOf method.
The remaining code is fairly self explanatory. If the incoming json matches our first case (it is a JsObject and there are fields not on the case class) then we return a JsError. Otherwise we pass it on to the underlying reader.
I am learning more and more about Scala and that nice playframework. But there are some things that bother me and that I can't get to work.
I like using Generics for some kind of collections, for example. But I need those to be stored in our database, in JSON. There is this cool auto conversion thing, but it does not work for generics, in no way I have tried :-/
Okay, to be concrete, code first:
case class InventorySlot(id: Long, item: Option[Item])
object InventorySlot {
implicit val fmt = Json.format[InventorySlot]
}
case class Inventory[T <: Item](slots: Vector[InventorySlot]) {
def length = slots.length
def items: Vector[T] = slots.map(slot => slot.item).flatten.asInstanceOf[Vector[T]]
def item(id: Long): Option[T] = {
slots.find(_.id == id) match {
case Some(slot: InventorySlot) =>
Some(slot.item.asInstanceOf[T])
case None =>
Logger.warn(s"slot with id $id not found")
None
}
}
}
object Inventory {
implicit val fmt = Json.format[Inventory]
}
Item is a basic abstract class of different items that can be put in that inventory. It doesn't matter. But sometimes I want to have an inventory, that just works for ItemType A, lets call it AItem.
So I want to create my inventory with something like this:
val myInventory = Inventory[AItem]("content of vector here") and when I call myInventory.item(2), then I want to get the item in slot 2, and it should be an object of type AItem, not just Item. (That's the reason why I am using generics here)
So the problem
The implicit format for Inventory does not work, obviously.
Item does, also with all special items, I can post the code for it below, and InventorySlot should work as well.
The error when compiling is:
Error:(34, 34) Play 2 Compiler:
C:\depot\mars\mainline\server\app\models\Test.scala:34: class Inventory takes type parameters
implicit val fmt = Json.format[Inventory]
^
I tried to write the read and write explicitly, like
implicit val fmt = (
(__ \ "slots").format[Vector[InventorySlot]]
)(Inventory.apply, unlift(Inventory.unapply))
wich is not even working in my IDE, and I can't find the problem.
I am confused. I don't know where my error lies, or if I am doing something wrong, or if I just miss something.
Any help will be appreciated.
I am so helpless, I even have considered doing a class for all possible inventory types, like
case class AItemInventory(protected var slots: Vector[InventorySlot]) extends Inventory[AItem](slots)
object AItemInventory {
implicit val fmt = Json.format[AItemInventory]
}
wich works. No problems, everything fine. So... I don't understand. Why is this working if it seems to be exactly the same, just hardcoded?
Appendix
The item formatter, wich works:
implicit val itemFormat = new Format[Item] {
override def reads(json: JsValue): JsResult[Item] = {
(json \ "itemType").as[ItemType] match {
case ItemType.AITEM => fmtAItem.reads(json)
}
}
override def writes(item: Item): JsValue = item match {
case subItem: AItem => fmtAItem.writes(subItem)
case _ => JsNumber(item.itemType.id)
}
}
object Inventory {
implicit def fmt[T <: Item](implicit fmt: Format[T]): Format[Inventory[T]] = new Format[Inventory[T]] {
def reads(json: JsValue): Inventory[T] = new Inventory[T] (
(json \ "blah").as[String]
)
def writes(i: Inventory[T]) = JsObject(Seq(
"blah" -> JsString(i.blah)
))
}
}
Source: documentation explains how to do it for reads and writes, and what I've done here is to combine these two for the format.
[edit]
So, i got a quick and dirty solution, thanks to Edmondo1984, I don't know if it's the best solution. I don't handle null values with pattern matching at the write function. You can read more details about my problem after this editing. Here is my code now:
object DBNames extends DefaultJsonProtocol {
implicit val pkFormat: JsonFormat[Pk[Int]] = new JsonFormat[Pk[Int]] {
def write(obj: Pk[Int]): JsValue = JsNumber(obj.get)
def read(json: JsValue): Pk[Int] = json.asJsObject.getFields("id") match {
case Seq(JsNumber(id)) => new Pk[Int] { id.toInt }
case _ => throw new DeserializationException("Int expected")
}
}
implicit val nameFormat = jsonFormat2(Name)
jsonFormat2 will implicitly use pkFormat to parse Pk[Int] values.
In my controller class I have this:
def listNames() = Action {
val names = DBNames.findAll()
implicit val writer = DBNames.nameFormat
var json = names.toJson
Ok(json.toString()).as("application/json")
}
I had to get the nameFormat from my model and make it implicit, so bars.toJson could use it to parse the Seq[Name] names.
[/edit]
I'm trying to use Play! Framework with Scala, I'm new to Scala programming and Play Framework, and everything seems nice, but I'm working on this problem during several hours and didn't find a solution.
I have a Case Class:
case class Name (id: Pk[Int], name: String)
And an object to deal with MySql. I created a implicit val nameFormat = jsonFormat2(Name) to deal with JSON.
object DBNames extends DefaultJsonProtocol {
implicit val nameFormat = jsonFormat2(Name)
var parser =
{
get[Pk[Int]]("id") ~
get[String]("name") map {
case id ~ name => Name(id,name)
}
}
def findAll():Seq[Name] =
{
DB.withConnection {
implicit connection =>
SQL("select * from names").as(DBNames.parser *)
}
}
def create(name: Name){
DB.withConnection {
implicit connection =>
SQL("insert into names (name) values ({name})").on(
'name -> name.name
).executeUpdate()
}
}
}
But when I try to compile it, Play! gives me this result:
[error] D:\ProjetosJVM\TaskList\app\models\Names.scala:20: could not find implicit value for evidence parameter of type models.DBNames.JF[anorm.Pk[Int]]
It seems like he couldn't find a way to parse the id value, since it is a Pk[Int] value.
So, by reading this: https://github.com/spray/spray-json I didn't found a way to parse it without creating a complete object parser like they show in the documentation:
object MyJsonProtocol extends DefaultJsonProtocol {
implicit object ColorJsonFormat extends RootJsonFormat[Color] {
def write(c: Color) = JsObject(
"name" -> JsString(c.name),
"red" -> JsNumber(c.red),
"green" -> JsNumber(c.green),
"blue" -> JsNumber(c.blue)
)
def read(value: JsValue) = {
value.asJsObject.getFields("name", "red", "green", "blue") match {
case Seq(JsString(name), JsNumber(red), JsNumber(green), JsNumber(blue)) =>
new Color(name, red.toInt, green.toInt, blue.toInt)
case _ => throw new DeserializationException("Color expected")
}
}
}
}
I have a "big" (actually small) project where I want to make most of things work with Ajax, so I think this is not a good way to do it.
How can I deal with JSON objects in this project, where almost all case classes will have a "JSON parser", without creating large ammounts of code, like the snippet above? And also, how can I make it work with an Seq[Name]?
You don't need to write a complete parser. The compiler says:
[error] D:\ProjetosJVM\TaskList\app\models\Names.scala:20: could not find implicit
value for evidence parameter of type models.DBNames.JF[anorm.Pk[Int]]
The scala compiler is looking for an implicit parameter of type JF[anorm.Pk[Int]] and there is no such an implicit parameter in scope. What is JF[anorm.Pk[Int]]? Well, you need to know the library and I didn't, so I had browsed spray-json source and found out:
trait StandardFormats {
this: AdditionalFormats =>
private[json] type JF[T] = JsonFormat[T] // simple alias for reduced verbosity
so JF[T] is just an alias for JsonFormat[T]. It all make sense: PK[Int] is a class coming from Anorm and spray-json provides out-of-the-box json support for standard types, but does not even know Anorm exists So you have to code your support for Pk[Int] and make it implicit in scope.
You will have code like the following:
object DBNames extends DefaultJsonProtocol {
implicit val pkFormat : JsonFormat[Pk[Int]] = new JsonFormat[Pk[Int]] {
//implementation
}
// rest of your code
}
If you have just started with Scala, you would probably have to read more about implicits and their resolution. I am providing you with a minimal answer: once you have provided the right implementation, your code will compile. I suggest you to refer to the javadoc of anorm.Pk and of JsonFormat to understand how to implement it correctly for your type.
Pk looks like scala.Option and in StandardFormats source code inside spray-json you find the JsonFormat implementation for Option, from which you can copy