playframework 2 - empty string for optional form field - scala

I have a form
case class UserUpdateForm(
id:Long, name: String,
remark: Option[String], location: Option[String])
I define the fields as
"id" -> of[Long],
"remarks" -> optional(text)
the remark field is None, Not Some("") I am excepting,
So, how can I bind an empty string to optional text field

case class OptionalText(wrapped: Mapping[String], val constraints: Seq[Constraint[Option[String]]] = Nil) extends Mapping[Option[String]] {
override val format: Option[(String, Seq[Any])] = wrapped.format
val key = wrapped.key
def verifying(addConstraints: Constraint[Option[String]]*): Mapping[Option[String]] = {
this.copy(constraints = constraints ++ addConstraints.toSeq)
}
def bind(data: Map[String, String]): Either[Seq[FormError], Option[String]] = {
data.keys.filter(p => p == key || p.startsWith(key + ".") || p.startsWith(key + "[")).map(k => data.get(k)).collect { case Some(v) => v }.headOption.map { _ =>
wrapped.bind(data).right.map(Some(_))
}.getOrElse {
Right(None)
}.right.flatMap(applyConstraints)
}
def unbind(value: Option[String]): (Map[String, String], Seq[FormError]) = {
val errors = collectErrors(value)
value.map(wrapped.unbind(_)).map(r => r._1 -> (r._2 ++ errors)).getOrElse(Map.empty -> errors)
}
def withPrefix(prefix: String): Mapping[Option[String]] = {
copy(wrapped = wrapped.withPrefix(prefix))
}
val mappings: Seq[Mapping[_]] = wrapped.mappings
}
val textOpt = new OptionalText(text)
Finally I copied the OptionalMapping class and exclude the empty filter part

I stumbled upon the same thing some months ago. I didn't find any easy way around it, so I decided to play along.
Basically, "remarks" -> optional(text)
will always return None when text is an empty string. So instead of treating an empty string as a sign of no updates, fill the remarks field in the form with the original value and then, after you get it back:
remarks match {
case None => // set remarks to ""
case originalRemark => // do nothing
case _ => // set remarks to the new value
}
Hope it helps. It's my first entry here, on Stack Overflow :)

Use
"remarks" -> default(text, "")

Related

Read Hocon config as a Map[String, String] with key in dot notation and value

I have following HOCON config:
a {
b.c.d = "val1"
d.f.g = "val2"
}
HOCON represents paths "b.c.d" and "d.f.g" as objects. So, I would like to have a reader, which reads these configs as Map[String, String], ex:
Map("b.c.d" -> "val1", "d.f.g" -> "val2")
I've created a reader and trying to do it recursively:
import scala.collection.mutable.{Map => MutableMap}
private implicit val mapReader: ConfigReader[Map[String, String]] = ConfigReader.fromCursor(cur => {
def concat(prefix: String, key: String): String = if (prefix.nonEmpty) s"$prefix.$key" else key
def toMap(): Map[String, String] = {
val acc = MutableMap[String, String]()
def go(
cur: ConfigCursor,
prefix: String = EMPTY,
acc: MutableMap[String, String]
): Result[Map[String, Object]] = {
cur.fluent.mapObject { obj =>
obj.value.valueType() match {
case ConfigValueType.OBJECT => go(obj, concat(prefix, obj.pathElems.head), acc)
case ConfigValueType.STRING =>
acc += (concat(prefix, obj.pathElems.head) -> obj.asString.right.getOrElse(EMPTY))
}
obj.asRight
}
}
go(cur, acc = acc)
acc.toMap
}
toMap().asRight
})
It gives me the correct result but is there a way to avoid MutableMap here?
P.S. Also, I would like to keep implementation by "pureconfig" reader.
The solution given by Ivan Stanislavciuc isn't ideal. If the parsed config object contains values other than strings or objects, you don't get an error message (as you would expect) but instead some very strange output. For instance, if you parse a typesafe config document like this
"a":[1]
The resulting value will look like this:
Map(a -> [
# String: 1
1
])
And even if the input only contains objects and strings, it doesn't work correctly, because it erroneously adds double quotes around all the string values.
So I gave this a shot myself and came up with a recursive solution that reports an error for things like lists or null and doesn't add quotes that shouldn't be there.
implicit val reader: ConfigReader[Map[String, String]] = {
implicit val r: ConfigReader[String => Map[String, String]] =
ConfigReader[String]
.map(v => (prefix: String) => Map(prefix -> v))
.orElse { reader.map { v =>
(prefix: String) => v.map { case (k, v2) => s"$prefix.$k" -> v2 }
}}
ConfigReader[Map[String, String => Map[String, String]]].map {
_.flatMap { case (prefix, v) => v(prefix) }
}
}
Note that my solution doesn't mention ConfigValue or ConfigReader.Result at all. It only takes existing ConfigReader objects and combines them with combinators like map and orElse. This is, generally speaking, the best way to write ConfigReaders: don't start from scratch with methods like ConfigReader.fromFunction, use existing readers and combine them.
It seems a bit surprising at first that the above code works at all, because I'm using reader within its own definition. But it works because the orElse method takes its argument by name and not by value.
You can do the same without using recursion. Use method entrySet as following
import scala.jdk.CollectionConverters._
val hocon =
"""
|a {
| b.c.d = "val1"
| d.f.g = val2
|}""".stripMargin
val config = ConfigFactory.load(ConfigFactory.parseString(hocon))
val innerConfig = config.getConfig("a")
val map = innerConfig
.entrySet()
.asScala
.map { entry =>
entry.getKey -> entry.getValue.render()
}
.toMap
println(map)
Produces
Map(b.c.d -> "val1", d.f.g -> "val2")
With given knowledge, it's possible to define a pureconfig.ConfigReader that reads Map[String, String] as following
implicit val reader: ConfigReader[Map[String, String]] = ConfigReader.fromFunction {
case co: ConfigObject =>
Right(
co.toConfig
.entrySet()
.asScala
.map { entry =>
entry.getKey -> entry.getValue.render()
}
.toMap
)
case value =>
//Handle error case
Left(
ConfigReaderFailures(
ThrowableFailure(
new RuntimeException("cannot be mapped to map of string -> string"),
Option(value.origin())
)
)
)
}
I did not want to write custom readers to get a mapping of key value pairs. I instead changed my internal data type from a map to list of pairs (I am using kotlin), and then I can easily change that to a map at some later internal stage if I need to. My HOCON was then able to look like this.
additionalProperties = [
{first = "sasl.mechanism", second = "PLAIN"},
{first = "security.protocol", second = "SASL_SSL"},
]
additionalProducerProperties = [
{first = "acks", second = "all"},
]
Not the best for humans... but I prefer it to having to build custom parsing components.

Replacing values from list of custom objects with map values

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.

Scala map validation

My program receives a scala map, the requirements is to validate this map (key-value pairs). Ex: validate a key value, convert its type from string to int etc. In a rare case, we update the key as well before passing the map to the down layer.
Its not always required to update this map , but only when we detect that there are any unsupported keys or values.
I'm doing some thing like this:
private def updateMap ( parameters: Map[String, String]): Map[String, String] = {
parameters.map{
case(k,v) => k match { case "checkPool" =>
(k, (if (k.contains("checkPool"))
v match {
case "1" => "true"
case _ => "false"
}
else v))
case "Newheader" => (k.replace("Newheader","header"),v)
case _ =>(k,v)
}
case _ => ("","")
}
}
Like this the code increases for doing the validation and converting the keys/values to supported ones.
Is there a cleaner way of doing this validation in Scala for a map?
Regards
According to what I understood from your question, match case can be your solution
inOptions.map(kv => kv.keySet.contains(STR) match {
case true => mutable.HashMap(STR_UPDT->kv.get(STR).get)
case _ => kv
})
Edited
Since you updated your question with more requirements, simple if else condition matching seems to be the best choice.
def updateMap(parameters: Map[String, String]): Map[String, String] = {
parameters.map(kv => {
var key = kv._1
var value = kv._2
if(key.contains("checkPool")){
value = if(value.equals("1")) "true" else "false"
}
else if(key.contains("Newheader")){
key = key.replace("Newheader", "header")
}
(key, value)
})
}
You can add more else if conditions

Scala list validSelectValues not updated

This is my code in a class Building:
object province extends MappedLongForeignKey(this, Province) {
override def dbColumnName = "province_id"
def selectValues: Box[List[(String, String)]] =
Full((("0", "")) +: Province.findAll(OrderBy(Province.name, Ascending)).
map( p => (p.id.is.toString, p.name.is)))
override def _toForm: Box[Elem] = Full(selectValues.flatMap{
case Nil => Empty
case xs => Full(SHtml.ajaxSelect(xs, Full(this.is.toString), v => {
this.set(v.toLong)
JsCmds.ReplaceOptions("council_select",
councilsList(v).map(c => (c.id.is.toString, c.name.is)), None)
}))
}.openOr(<span>{"sin provincias"}</span>))
private def councilsList(p: String) = p match {
case id if id != "" =>
Council.findAll(By(Council.province, p.toLong),
OrderBy(Council.name, Ascending))
case _ => List()
}
}
// Council
object council extends MappedLongForeignKey(this, Council) {
override def dbColumnName = "council_id"
val id = "council_select"
override def validSelectValues: Box[List[(Long, String)]] =
Full(((0l, "")) +: Council.findAll(By(Council.province, province),
OrderBy(Council.name, Ascending)).
map( c => (c.id.is, c.name.is)))
override def _toForm: Box[Elem] = Full(validSelectValues.flatMap{
case Nil => Empty
case xs => Full(SHtml.selectObj(xs, Full(this.is), this.set, "id" -> id))
}.openOr(<select id={id}/>))
}
in the edit form, when I change on the list the province, council list is replaced by province filter perfectly, but when i save building, councill is not saved/updated the first time. the next times, it save perfect because the province is seted, until i change province other time... So, when I set a new province, the councill isnt saved, I need to save 2 times, 1 for province, and 1 for council.
I think that the problem is because validSelectValues of object council, is loaded the first time i load the page, and it will be filtering by the province seted, or null list when province is not set... how can I reload the council validSelectValues when i change the province list?
Thanks
Try replacing your call to ajaxSelect with ajaxUntrustedSelect. The normal select options in Lift check that the value being submitted is trusted, IE: that it was one of the options available when the page was generated. If you are using javascript or other means to mutate the list, you can avoid that check by using the Untrusted version of the component.
The solution that I have find...
object council extends MappedLongForeignKey(this, Council) {
override def dbColumnName = "council_id"
val id = "council_select"
def selectValues: Box[List[(String, String)]] = province.obj match {
case Full(o) => Full( Council.findAll(By(Council.province, o),
OrderBy(Council.name, Ascending)).
map( c => (c.id.is.toString, c.name.is)))
case _ => Full((("0","")) Council.findAll(OrderBy(Council.name, Ascending)).
map( c => (c.id.is.toString, c.name.is)))
}
override def _toForm: Box[Elem] = Full(selectValues.flatMap{
case Nil => Empty
case xs => Full(SHtml.untrustedSelect(xs, Full(this.is.toString), v => {this.set(v.toLong)}, "id" -> id))
}.openOr(<select id={id}/>))
}

pattern matching on a series of values in scala

I'm a Scala beginner and this piece of code makes me struggle.
Is there a way to do pattern matching to make sure everything i pass to Data is of the correct type? As you can see i have quite strange datatypes...
class Data (
val recipient: String,
val templateText: String,
val templateHtml: String,
val blockMaps: Map[String,List[Map[String,String]]],
templateMap: Map[String,String]
)
...
val dataParsed = JSON.parseFull(message)
dataParsed match {
case dataParsed: Map[String, Any] => {
def e(s: String) = dataParsed get s
val templateText = e("template-text")
val templateHtml = e("template-html")
val recipient = e("email")
val templateMap = e("data")
val blockMaps = e("blkdata")
val dependencies = new Data(recipient, templateText, templateHtml, blockMaps, templateMap)
Core.inject ! dependencies
}
...
I guess your problem is you want to be able to patten match the map that you get from parseFull(), but Map doesn't have an unapply.
So you could pattern match every single value, providing a default if it is not of the correct type:
val templateText: Option[String] = e("template-text") match {
case s: String => Some(s)
case _ => None
}
Or temporarily put all the data into some structure that can be pattern matched:
val data = (e("template-text"), e("template-html"), e("email"), e("data"),
e("blkdata"))
val dependencies: Option[Data] = data match {
case (templateText: String,
templateHtml: String,
blockMaps: Map[String,List[Map[String,String]]],
templateMap: Map[String,String]) =>
Some(new Data(recipient, templateText, templateHtml, blockMaps, templateMap))
case _ => None
}