Need to validate nested json arrays in play 2.1 in scala - scala

I was able to perform simple validations on simple json structures like this one:
object RestTest extends Controller {
case class Address(street: String,
number: Int)
case class Person(name: String,
age: Int,
address: Address)
implicit val address = Json.reads[Address]
implicit val rds = Json.reads[Person]
def restTest = Action(parse.json) {
request =>
request.body.validate[Person].map {
case person => Ok(Json.obj("e" -> 0, "message" -> ("The name is: " + person.name + " and he lives in " + person.address.street)))
}.recoverTotal(e => Ok("e" -> 1)
}
}
Now I have the following structure that contains arrays, but I wasn't able to validate it correctly so far. I have tried many different ways, but I keep receiving compilation errors.
case class SecondStructure(index: Int)
case class EntryStructure(field1: String,
muSecondJsonArray: List[SecondStructure])
case class MyJsonArray(allEntries: List[EntryStructure])
How can I validate this json?
Thanks

First of all, ensure you are using the latest Play 2.1.1 releases. There was an issue with earlier versions when validating case classes with a single field. After that, it should all work - please see below for an example:
object JsonTest {
case class SecondStructure(index: Int)
case class EntryStructure(field1: String, muSecondJsonArray: List[SecondStructure])
case class MyJsonArray(allEntries: List[EntryStructure])
// Use the macro "inception" feature to automatically build your Readers.
implicit val ssReads = Json.reads[SecondStructure]
implicit val esReads = Json.reads[EntryStructure]
implicit val arrayReads = Json.reads[MyJsonArray]
// Defining an example instance...
val testArray = MyJsonArray(
List(
EntryStructure("foo", List(SecondStructure(1), SecondStructure(2))),
EntryStructure("bar", List(SecondStructure(3), SecondStructure(4)))))
// And the equivilant JSON structure...
val testJson = Json.obj("allEntries" ->
Json.arr(
Json.obj("field1" -> "foo", "muSecondJsonArray" -> Json.arr(
Json.obj("index" -> 1), Json.obj("index" -> 2))),
Json.obj("field1" -> "bar", "muSecondJsonArray" -> Json.arr(
Json.obj("index" -> 3), Json.obj("index" -> 4)))))
testJson.validate[MyJsonArray].map {
case foo if foo == testArray => println("Okay, we're good!")
}
}

Related

Scala Cat library validation list group by Error code

I am new to Scala and functional programming.I did one validation using Scala cat library. But I can't able to group Invalid elements by error code.
case class Err(code: ErrorCode, elementName: String)
This is the output of invalid elements.
List(Invalid(NonEmptyList(Err(missingElement,Des), Err(InvalidElement,order), Err(InvalidElement,name), Err(InvalidElement,source))))
what I want is something like this
missingElement->List(Des)
InvalidElement->List(order,name,source)
Is there any functionality in cat library itself for this? Or is there any workaround to get this?
Assuming your full structure looks similar to this:
sealed trait ErrorCode
object ErrorCode {
case object MissingElement extends ErrorCode
case object InvalidElement extends ErrorCode
}
final case class Err(code: ErrorCode, elementName: String)
And we have a ValidatedNel[Err, ?]:
val res: ValidatedNel[Err, String] =
Invalid(
NonEmptyList(
Err(ErrorCode.MissingElement, "Des"),
List(
Err(ErrorCode.InvalidElement, "order"),
Err(ErrorCode.InvalidElement, "name"),
Err(ErrorCode.InvalidElement, "source")
)
)
)
Then what you'd need to do in order to group the list would be to use groupBy on the error side, using leftMap:
val groupedErrs: Validated[Map[ErrorCode, List[String]], String] =
res.leftMap(
_.toList
.groupBy(_.code)
.map { case (code, errs) => code -> errs.map(_.elementName) }
)
Result:
Invalid(Map(InvalidElement -> List(order, name, source), MissingElement -> List(Des)))
If all this is inside a list of validations, List[ValidatedNel[Err, ?]], then we just need an additional map operation to iterate the list:
val groupedErrs: List[Validated[Map[ErrorCode, List[String]], String]] =
res.map(
_.leftMap(
_.toList
.groupBy(_.code)
.map { case (code, errs) => code -> errs.map(_.elementName) }
)
)
Result:
List(Invalid(Map(InvalidElement -> List(order, name, source), MissingElement -> List(Des))))

Elastic4s search case class example errors when result document is missing a field

I've been working with this example from the Elastic4s manual. It is working fine until it attempts to retrieve a document that does not have a field specified in the case class.
In this example from the manual, let's say one result only had name and was missing the location field. It would yield this error:
java.util.NoSuchElementException: key not found: location
I'm looking for a good approach to deal with search results that have varying fields.
Code sample:
case class Character(name: String, location: String)
implicit object CharacterHitAs extends HitAs[Character] {
override def as(hit: RichSearchHit): Character = {
Character(hit.sourceAsMap("name").toString, hit.sourceAsMap("location").toString) }}
val resp = client.execute {
search in "gameofthrones" / "characters" query "kings landing"
}.await
val characters :Seq[Character] = resp.as[Character]
When developing a case class with optional parameters, use Option:
case class Character(name: String, location: Option[String])
Character("Tyrion Lannister", None)
Then all you have to do is modify your data extractor to pass a None Option if it doesn't find the data:
val tyrion = Map("location" -> "King's Landing", "name" -> "Cersei Lannister")
val cersei = Map("father" -> "Tywin Lannister?", "name" -> "Cersei Lannister")
val jaime = Map("father" -> "Tywin Lannister", "location" -> "Tower of the Hand")
val characters = List(tyrion, cersei, jaime)
case class Character(name: String, location: Option[String])
characters.map(x => Character(x.getOrElse("name", "A CHARACTER HAS NO NAME"), x.get("location")))
The result of characters.map(...) is this:
res0: List[Character] = List(
Character(Cersei Lannister,Some(King's Landing)),
Character(Cersei Lannister,None),
Character(A CHARACTER HAS NO NAME NAME,Some(Tower of the Hand)))
From the source code for RichSearchHit, sourceAsMap should return a Map object:
def sourceAsMap: Map[String, AnyRef] = if (java.sourceAsMap == null) Map.empty else java.sourceAsMap.asScala.toMap
Given that you're using a Map shorthand, you should be able to convert your code to:
case class Character(name: String, location: Option[String])
implicit object CharacterHitAs extends HitAs[Character] {
override def as(hit: RichSearchHit): Character = {
Character(hit.sourceAsMap.getOrElse("name", "A CHARACTER HAS NO NAME"), hit.sourceAsMap.get("location")) }}

withDefaultValue in case class

simple case class:
case class country(name: String, townPopulation: Map[String,Int])
with simple example:
scala> val germany = country("Germany",Map("Berlin" -> 100000, "Saale" -> 4000))
germany: country = country(Germany,Map(Berlin -> 100000, Saale -> 4000))
scala> germany.townPopulation("Berlin")
res77: Int = 100000
scala> germany.townPopulation("blergh")
java.util.NoSuchElementException: key not found: blergh
at scala.collection.MapLike$class.default(MapLike.scala:228)
at scala.collection.AbstractMap.default(Map.scala:59)
at scala.collection.MapLike$class.apply(MapLike.scala:141)
at scala.collection.AbstractMap.apply(Map.scala:59)
... 42 elided
I would like to return 0 for towns that dont exist, this can be done when declaring a val:
scala> val germany = country("Germany",Map("Berlin" -> 100000, "Saale" -> 4000).withDefaultValue(0))
germany: country = country(Germany,Map(Berlin -> 100000, Saale -> 4000))
scala> germany.townPopulation("fdhjkjhkhjdfg")
res79: Int = 0
but I can not figure out how to do it in one place, at least not when it is a case class, I would like something simple as the following, but I am obviously doing it wrong:
scala> case class country(name: String, townPopulation: Map[String,Int].withDefaultValue(0))
<console>:1: error: ')' expected but '.' found.
case class country(name: String, townPopulation: Map[String,Int].withDefaultValue(0))
^
<console>:1: error: ';' expected but ')' found.
case class country(name: String, townPopulation: Map[String,Int].withDefaultValue(0))
Is there a short simple path to a solution that has 0 as defaultValue always?
I see few possible ways:
add auxiliary method which encapsulate logic of default value
def population(town : String) : Int = townPopulation.getOrElse(town, 0)
add method to companion object with same purpose
def withDefault(name: String, townPopulation: Map[String, Int]) : country =
country(name, townPopulation.withDefaultValue(0))
Use map.get(), which returns an Option:
println germany.townPopulation.get("blergh").getOrElse(0)
// or, more concisely:
println germany.townPopulation.getOrElse("blergh", 0)
Ah, on re-reading your question, you want to hard-code the default value in the case class. I guess you'll have to mess with the apply() method.
val germany = country("Germany",
Map("Berlin" -> 100000, "Saale" -> 4000)
.withDefaultValue(0))
Edit (after OP's answer):
My bad! Should have read more thoroughly your question.
As stated in this SO question: You do not have the option of changing the way the default constructor stores its parameters (e.g. by modifying the parameters before they are stored as vals) […].
An alternate solution would be to declare a regular class along with its companion object:
class Country(val name: String, val townPopulation: Map[String, Int])
case object Country {
def apply(name: String, townPopulation: Map[String, Int]) =
new Country(name, townPopulation.withDefaultValue(0))
}
This would allow you to declare countries using the nice syntax:
val germany = Country("Germany", Map("Berlin" -> 100000, "Saale" -> 4000))
assert(germany.townPopulation("Berlin") == 100000)
assert(germany.townPopulation("Blergh") == 0)
But note that, as it is not a case class you won't get the usual case class perks such as:
// Compiler will give you
// "object Country is not a case class,
// nor does it have an unapply/unapplySeq member"
//germany match {
// case Country(a, b) => println("It won't compile! Not a case class")
//}
Depending on your use case, you could go the long hard road and implement methods unapply and unapplySeq to retrieve such behavior if needed!

Scala Macros: Checking for a certain annotation

Thanks to the answers to my previous question, I was able to create a function macro such that it returns a Map that maps each field name to its value of a class, e.g.
...
trait Model
case class User (name: String, age: Int, posts: List[String]) extends Model {
val numPosts: Int = posts.length
...
def foo = "bar"
...
}
So this command
val myUser = User("Foo", 25, List("Lorem", "Ipsum"))
myUser.asMap
returns
Map("name" -> "Foo", "age" -> 25, "posts" -> List("Lorem", "Ipsum"), "numPosts" -> 2)
This is where Tuples for the Map are generated (see Travis Brown's answer):
...
val pairs = weakTypeOf[T].declarations.collect {
case m: MethodSymbol if m.isAccessor =>
val name = c.literal(m.name.decoded)
val value = c.Expr(Select(model, m.name))
reify(name.splice -> value.splice).tree
}
...
Now I want to ignore fields that have #transient annotation. How would I check if a method has a #transient annotation?
I'm thinking of modifying the snippet above as
val pairs = weakTypeOf[T].declarations.collect {
case m: MethodSymbol if m.isAccessor && !m.annotations.exists(???) =>
val name = c.literal(m.name.decoded)
val value = c.Expr(Select(model, m.name))
reify(name.splice -> value.splice).tree
}
but I can't find what I need to write in exists part. How would I get #transient as an Annotation so I could pass it there?
Thanks in advance!
The annotation will be on the val itself, not on the accessor. The easiest way to access the val is through the accessed method on MethodSymbol:
def isTransient(m: MethodSymbol) = m.accessed.annotations.exists(
_.tpe =:= typeOf[scala.transient]
)
Now you can just write the following in your collect:
case m: MethodSymbol if m.isAccessor && !isTransient(m) =>
Note that the version of isTransient I've given here has to be defined in your macro, since it needs the imports from c.universe, but you could factor it out by adding a Universe argument if you're doing this kind of thing in several macros.

Jerkson. Serializing map to JsValue

I am using built-in jerson with playframework 2, and all I want is to serialize map, containing values of different type:
object AppWriter extends Writes[Application] {
def writes(app: Application): JsValue = {
Json.toJson(Map(
"id" -> app.getId.toString,
"name" -> app.getName,
"users" -> Seq(1, 2, 3)
))
}
}
In this case I have:
No Json deserializer found for type scala.collection.immutable.Map[java.lang.String,java.lang.Object].
Try to implement an implicit Writes or Format for this type
Navigatin through framework code shows that there is serializer for Map[String,V] types implicit def mapWrites[V] .. , but I cannot understand why it doesn't applied.
Can anybody help me?
UPD: I found simple workaround:
object AppWriter extends Writes[Application] {
def writes(app: Application): JsValue = {
Json.toJson(Map[String, JsValue](
"id" -> JsNumber(BigDecimal(app.getId)),
"name" -> JsString(app.getName),
"users" -> JsArray(Seq(1, 2, 3).map(x => JsNumber(x)))
))
}
}
but this is not so elegant...
The standard way to do this is by creating a JsObject from the individual key-value pairs for the fields—not by putting the pairs into a map. For example, assuming your Application looks like this:
case class Application(getId: Int, getName: String)
You could write:
import play.api.libs.json._, Json.toJson
implicit object AppWriter extends Writes[Application] {
def writes(app: Application): JsValue = JsObject(
Seq(
"id" -> JsNumber(app.getId),
"name" -> JsString(app.getName),
"users" -> toJson(Seq(1, 2, 3))
)
)
}
And then:
scala> toJson(Application(1, "test"))
res1: play.api.libs.json.JsValue = {"id":1,"name":"test","users":[1,2,3]}
Note that you don't need to spell out how to serialize the Seq[Int]—the default Format instances will do the work for you.