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))))
Related
I'm using the scopt library: https://github.com/scopt/scopt
I see that I can define a case class with option names if I know them beforehand. What if I don't know the names of the options that the user will pass? Is there any way for me to capture those option values in a dictionary or something?
Thanks!
Try opt[Map[String,String]] like so
case class Config(args: Map[String, String] = Map())
object Hello extends App {
val parser = new scopt.OptionParser[Config]("scopt") {
head("scopt", "3.x")
opt[Map[String,String]]("args").valueName("k1=v1,k2=v2...").action( (x, c) =>
c.copy(args = x) ).text("other arguments")
}
parser.parse(args, Config()) match {
case Some(config) => println(config)
case None =>
// arguments are bad, error message will have been displayed
}
}
which on executing with
run --args key1=val1,key2=val2
outputs
Config(Map(key1 -> val1, key2 -> val2))
I have a quite odd problem to solve, I have a String, a custom Type and a Map of Maps.
The string needs to have a few values replaced based on mapping between a value in custom type (which is a key in the map of maps).
This is the current structure:
case class Students(favSubject: String)
val mapping: Map[String, Map[String, String]] = Map("John" -> Map("English" -> "Soccer"))
val studentInfo: List[Students] = List(Students("English"))
val data: String = "John is the favourite hobby"
I tried the following:
mapping.foldLeft(data){ case (outputString, (studentName, favSubject)) => outputString.replace(studentName, favSubject.getOrElse(studentInfo.map(x => x.favSubject).toString, "")) }
What I need to get is:
"Soccer is the favourite hobby"
What I get is:
" is the favourite hobby"
So looks like I am getting the map of maps traversal right but the getOrElse part is having issues.
What I would do, would be to first change the structure of mappings so it makes more sense for the problem.
val mapping: Map[String, Map[String, String]] = Map("John" -> Map("English" -> "Soccer"))
val mapping2 =
mapping.iterator.flatMap {
case (student, map) => map.iterator.map {
case (info, value) => (info, student, value)
}
}.toList
.groupBy(_._1)
.view
.mapValues { group =>
group.iterator.map {
case (_, student, value) => student -> value
}.toList
}.toMap
// mapping2: Map[String, List[(String, String)]] = Map("English" -> List(("John", "Soccer")))
Then I would just traverse the students informativo, making all the necessary replacements.
final case class StudentInfo(favSubject: String)
val studentsInformation: List[StudentInfo] = List(StudentInfo("English"))
val data: String = "John is the favourite hobby"
val result =
studentsInformation.foldLeft(data) { (acc, info) =>
mapping2
.getOrElse(key = info.favSubject, default = List.empty)
.foldLeft(acc) { (acc2, tuple) =>
val (key, replace) = tuple
acc2.replace(key, replace)
}
}
// result: String = "Soccer is the favourite hobby"
When you map() a List, you get a List back. It's toString has a format "List(el1,el2,...)". Surely you cannot use it as a key for your sub-map, you would want just el1.
Here is a version of the working code. It might not be a solution you are looking for(!), just a solution to your question:
case class Students(favSubject: String)
val mapping: Map[String, Map[String, String]] = Map("John" -> Map("English" -> "Soccer"))
val studentInfo: List[Students] = List(Students("English"))
val data: String = "John is the favourite hobby"
val res = mapping.foldLeft(data) {
case (outputString, (studentName, favSubjectDict)) =>
outputString.replace(
studentName,
favSubjectDict.getOrElse(studentInfo.map(x => x.favSubject).head, "?")
)
}
println(s"$res") //prints "Soccer is the favourite hobby"
val notMatchingSubject = studentInfo.map(x => x.favSubject).toString
println(s"Problem in previous code: '$notMatchingSubject' !== 'English'")
Try it here: https://scastie.scala-lang.org/flQNRrUQSXWPxSTXOPPFgA
The issue
It is a bit unclear why StudentInfo is a List in this form... If I guessed, it was designed to be a list of StudentInfo containing both, name and favSubject and you would need to search it by name to find favSubject. But it is just a guess.
I went with the simplest working solution, to get a .head (first element) of the sequence from the map. Which will always be "English" even if you add more Studends to the list.
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")) }}
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!")
}
}
Is it possible to make json4s not to throw exception when required field is missing ?
When I "extract" object from raw json string it throws exception like this one
org.json4s.package$MappingException: No usable value for pager
No usable value for rpp
Did not find value which can be converted into byte
at org.json4s.reflect.package$.fail(package.scala:98)
at org.json4s.Extraction$ClassInstanceBuilder.org$json4s$Extraction$ClassInstanceBuilder$$buildCtorArg(Extraction.scala:388)
at org.json4s.Extraction$ClassInstanceBuilder$$anonfun$11.apply(Extraction.scala:396)
Is it possible just to let it be null ?
It's quite simple, you have to use Option and its potentials, Some and None.
val json = ("name" -> "joe") ~ ("age" -> Some(35));
val json = ("name" -> "joe") ~ ("age" -> (None: Option[Int]))
Beware though, in the above case a match will be performed for your Option. If it's None, it will be completely removed from the string, so it won't feed back null.
In the same pattern, to parse incomplete JSON, you use a case class with Option.
case class someModel(
age: Option[Int],
name: Option[String]
);
val json = ("name" -> "joe") ~ ("age" -> None);
parse(json).extract[someModel];
There is a method which won't throw any exception, and that is extractOpt
parse(json).extractOpt[someModel];
A way to replicate that with the scala API would be to use scala.util.Try:
Try { parse(json).extract[someModel] }.toOption
I've dealt with this problem when dealing with data migrations, and I wanted default values to fill undefined fields.
My solution was to merge the defaults into the JValue before extracting the result.
val defaultsJson = Extraction.decompose(defaults)
val valueJson = JsonUtil.jValue(v)
(defaultsJson merge valueJson).extract[T]
JsonUtil.scala
import org.json4s._
object JsonUtil {
import java.nio.charset.StandardCharsets.UTF_8
import java.io.{InputStreamReader, ByteArrayInputStream}
def jValue(json: String): JValue = {
jValue(json.getBytes(UTF_8))
}
def jValue(json: Array[Byte]): JValue = {
val reader = new InputStreamReader(new ByteArrayInputStream(json), UTF_8)
native.JsonParser.parse(reader)
}
}