scala - Passing a function that takes another function as a parameter - function

I am trying to pass a function as a parameter, but that function has multiple arguments (one of which is a function).
Here is what I am trying to do in a basic Python example:
def first(string1, string2, func):
func(string1, string2, third)
def second(string1, string2, func):
func(string1, string2)
def third(string1, string):
# operations go here
first("one", "two", second)
My attempt at this in Scala was the following:
def first(string1: String, string2: String, func: (Any, Any, Any) => Unit) = {
func(string1, string2, func)
}
def second(string1: String, string2: String, func: (Any, Any) => Unit) = {
func(string1, string2)
}
def third(string1: String, string2: String) = {
// operations
}
def main(args: Array[String]): Unit = {
first("one", "two", second)
}
I get an error for trying to pass second into first with an insufficient amount of arguments. Is it possible to achieve this functionality in the same style as the Python example?
EDIT:
I tried replacing the body of my main method with first("one", "two", second _) and it gives me a type mismatch error
type mismatch; found : (String, String, (Any, Any, Any) => Unit) => Unit required: (Any, Any, Any) =>
Unit
Any idea what's going on here?

What you are trying to do is not type-safe. You cannot assign (String, String, (Any, Any) => Unit) => Unit to (Any, Any, Any) => Unit. If you could, then you could do the following:
val f = second _
val af: (Any, Any, Any) => Unit = f
af(1, "abc", 5)
You can do it if you specify the types more precisely:
def second(string1: String, string2: String, func: (String, String) => Unit) = {
func(string1, string2)
}
def third(string1: String, string2: String) = {
// operations
}
def first(string1: String, string2: String, func: (String, String, (String, String) => Unit) => Unit) = {
func(string1, string2, third)
}

Related

Scala Play: List to Json-Array

I've got a List which holds some Personalization-objects. The latter is defined like this:
sealed case class Personalization(firstname: String, lastname: String, keycardId: String)
I need to map this list to a Json-Array structure which has to look like this:
"personalization": [
{
"id": "firstname",
"value": "John"
},
{
"id": "lastname",
"value": "Doe"
}...
I am struggling with the part of mapping the field information to id/value pairs. Normally, I would create a play.api.libs.json.Format out of the Personalization class and let it map automatically -> Json.format[Personalization] - but this time, I need to create an array where an entry can hold n attributes.
Therefore I am asking for advice, if there is a possibility to use the Scala Play-Framework?
Any input is much appreciated, thank you!
Writing as such JSON representation is not quite complex, using Writes.transform.
import play.api.libs.json._
case class Personalization(firstname: String, lastname: String, keycardId: String) // No need to seal a case class
implicit def writes: Writes[Personalization] = {
val tx: JsValue => JsValue = {
case JsObject(fields) => Json.toJson(fields.map {
case (k, v) => Json.obj("id" -> k, "value" -> v)
})
case jsUnexpected => jsUnexpected // doesn't happen with OWrites
}
Json.writes[Personalization].transform(tx)
}
Which can be tested as bellow.
val personalization = Personalization(
firstname = "First",
lastname = "Last",
keycardId = "Foo")
val jsonRepr = Json.toJson(personalization)
// => [{"id":"firstname","value":"First"},{"id":"lastname","value":"Last"},{"id":"keycardId","value":"Foo"}]
Reading is a little bit tricky:
implicit def reads: Reads[Personalization] = {
type Field = (String, Json.JsValueWrapper)
val fieldReads = Reads.seq(Reads[Field] { js =>
for {
id <- (js \ "id").validate[String]
v <- (js \ "value").validate[JsValue]
} yield id -> v
})
val underlying = Json.reads[Personalization]
Reads[Personalization] { js =>
js.validate(fieldReads).flatMap { fields =>
Json.obj(fields: _*).validate(underlying)
}
}
}
Which can be tested as bellow.
Json.parse("""[
{"id":"firstname","value":"First"},
{"id":"lastname","value":"Last"},
{"id":"keycardId","value":"Foo"}
]""").validate[Personalization]
// => JsSuccess(Personalization(First,Last,Foo),)
Note that is approach can be used for any case class format.
Probably it is possible to do it a more elegant way I did, but you can use the following snippet:
case class Field(id: String, value: String)
object Field {
implicit val fieldFormatter: Format[Field] = Json.format[Field]
}
sealed case class Personalization(firstname: String, lastname: String, keycardId: String)
object Personalization {
implicit val personalizationFormatter: Format[Personalization] = new Format[Personalization] {
override def reads(json: JsValue): JsResult[Personalization] =
Try {
val data = (json \ "personalization").as[JsValue]
data match {
case JsArray(value) =>
val fields = value.map(_.as[Field]).map(f => f.id -> f.value).toMap
val firstname = fields.getOrElse("firstname", throw new IllegalArgumentException("Mandatory field firstname is absent."))
val lastname = fields.getOrElse("lastname", throw new IllegalArgumentException("Mandatory field lastname is absent."))
val keycardId = fields.getOrElse("keycardId", throw new IllegalArgumentException("Mandatory field keycardId is absent."))
Personalization(firstname, lastname, keycardId)
case _ => throw new IllegalArgumentException("Incorrect json format for Personalization.")
}
}.toEither.fold(e => JsError(e.getMessage), JsSuccess(_))
override def writes(o: Personalization): JsValue = {
val fields = List(Field("firstname", o.firstname), Field("lastname", o.lastname), Field("keycardId", o.keycardId))
JsObject(List("personalization" -> Json.toJson(fields)))
}
}
}
It converts {"personalization":[{"id":"firstname","value":"John"},{"id":"lastname","value":"Doe"},{"id":"keycardId","value":"1234"}]} to Personalization(John,Doe,1234) and vice versa

ScalaJson implicit Write, found: Any required: play.api.libs.json.Json.JsValueWrapper

I am building a web app using Scala / Play Framework and Reactive Mongo and I want the models to be defined in the database instead of having them hardcoded.
To do so, I am writing a class EntityInstance taking a Sequence of FieldInstance :
case class EntityInstance(fields: Seq[FieldInstance])
I am trying to accept fields from any types and to convert them to Json : example
new FieldInstance("name", "John") | json: { "name": "John" }
new FieldInstance("age", 18) | json: { "age": 18 }
At the moment I am trying to accept Strings, Booleans and Integers and if the type is not supported I write some error :
new FieldInstance("profilePicture", new Picture("john.jpg") | json: { "profilePicture": "Unsupported type
I wrote a FieldInstance class taking a fieldName as a String and a value as any type. As soon as that class is instantiated I cast the value to a known type or to the String describing the error.
class FieldInstance(fieldNamec: String, valuec: Any) {
val fieldName = fieldNamec
val value = valuec match {
case v: Int => v
case v: String => v
case v: Boolean => v
case _ => "Unrecognized type"
}
}
object FieldInstance {
implicit val fieldInstanceWrites = new Writes[FieldInstance] {
def writes(fieldInstance: FieldInstance) = Json.obj(
fieldInstance.fieldName -> fieldInstance.value
)
}
}
I created a companion object with an implicit Write to json so I can call "Json.toJson()" on an instance of FieldInstance and get a json as described on my examples above.
I get an error : found: Any required: play.api.libs.json.Json.JsValueWrapper
I understand that it comes from the fact that my value is of type Any but I thought the cast would change that Any to String || Boolean || Int before hitting the Writer.
PS: Ignore the bad naming of the classes, I could not name EntityInstance and FieldInstance, Entity and Field because these as the classes I use to describe my models.
I found a fix to my problem :
The type matching that I was doing in the class should be done in the implicit Write !
class FieldInstance(fieldNamec: String, valuec: Any) {
val fieldName = fieldNamec
val value = valuec
override def toString(): String = "(" + fieldName + "," + value + ")";
}
object FieldInstance {
implicit val fieldInstanceWrites = new Writes[FieldInstance] {
def writes(fieldInstance: FieldInstance) =
fieldInstance.value match {
case v: Int => Json.obj(fieldInstance.fieldName -> v.asInstanceOf[Int])
case v: String => Json.obj(fieldInstance.fieldName -> v.asInstanceOf[String])
case v: Boolean => Json.obj(fieldInstance.fieldName -> v.asInstanceOf[Boolean])
case _ => Json.obj(fieldInstance.fieldName -> "Unsupported type")
}
}
}
This code now allows a user to create an EntityInstance with Fields of Any type :
val ei = new EntityInstance(Seq[FieldInstance](new FieldInstance("name", "George"), new FieldInstance("age", 25), new FieldInstance("married", true)))
println("-- TEST ENTITY INSTANCE TO JSON --")
println(Json.toJson(ei))
prints : {"entity":[{"name":"George"},{"age":25},{"married":true}]}
Here is my EntityInstance code if you are trying to test it :
case class EntityInstance(fields: Seq[FieldInstance])
object EntityInstance {
implicit val EntityInstanceWrites = new Writes[EntityInstance] {
def writes(entityInstance: EntityInstance) =
Json.obj("entity" -> entityInstance.fields)
}
}
It is returning a String, Int or Boolean but Json.obj is expecting the value parameter of type (String, JsValueWrapper)
def obj(fields: (String, JsValueWrapper)*): JsObject = JsObject(fields.map(f => (f._1, f._2.asInstanceOf[JsValueWrapperImpl].field)))
a quick fix could be to convert the matched value v with toJson provided the implicit Writes[T] for type T is available (which they are for String, Int and Boolean)
class FieldInstance(fieldNamec: String, valuec: Any) {
val fieldName = fieldNamec
val value = valuec match {
case v: Int => Json.toJson(v)
case v: String => Json.toJson(v)
case v: Boolean => Json.toJson(v)
case _ => Json.toJson("Unrecognized type")
}
}
If you'd like to see which DefaultWrites are available you can browse them in the play.api.libs.json package in trait DefaultWrites
for example:
/**
* Serializer for Boolean types.
*/
implicit object BooleanWrites extends Writes[Boolean] {
def writes(o: Boolean) = JsBoolean(o)
}

How to prevent json4s from rendering null values?

How do I prevent json4s rendering null values when converting an object/JObject into a json string?
In Jackson you can do this by doing this:
mapper.setSerializationInclusion(Include.NON_NULL)
How can I do the same thing in json4s?
Example
import org.json4s.jackson.JsonMethods._
import org.json4s.jackson.Serialization
import org.json4s.{Extraction, NoTypeHints}
case class Book(title: String, author: String)
implicit val formats = Serialization.formats(NoTypeHints)
val bookJValue = Extraction.decompose(Book(null, "Arthur C. Clark"))
# JObject(List((title,JNull), (author,JString(Arthur C. Clark))))
val compacted = compact(render(bookJValue))
# {"title":null,"author":"Arthur C. Clark"}
I'd like the compacted json be this:
{"author":"Arthur C. Clark"}
case class Book(title: Option[String], author: String)
val b = Book(None, "Arthur C. Clark")
println(write(b))
res1:> {"author":"Arthur C. Clark"}
You can use Option to define variable. if it is none, it will not serialize this variable.
There is another way to do this by using removeField after you decompose your object, like:
bookJValue.removeFile {
case (_, JNull) => true
case _ => false
}
You can easily create your own custom serializer. Follow me.
First of all make small change in formats:
implicit val formats = DefaultFormats + new BookSerializer
After that build your own serializer/deserializer:
class BookSerializer extends CustomSerializer[Book](format => (
{
case JObject(JField("title", JString(t)) :: JField("author", JString(a)) ::Nil) =>
new Book(t, a)
},
{
case x # Book(t: String, a: String) =>
JObject(JField("title", JString(t)) ::
JField("author", JString(a)) :: Nil)
case Book(null, a: String) =>
JObject(JField("author", JString(a)) :: Nil) // `title` == null
}
))
The first part is deserializer (convert data from json to case class) and the second is serializer (conversion from case class to json). I've added case of title == null. You can easily add cases as many as you need.
The whole listing:
import org.json4s.jackson.JsonMethods._
import org.json4s.{DefaultFormats, Extraction}
import org.json4s._
case class Book(title: String, author: String)
implicit val formats = DefaultFormats + new BookSerializer
class BookSerializer extends CustomSerializer[Book](format => (
{
case JObject(JField("title", JString(t)) :: JField("author", JString(a)) ::Nil) =>
new Book(t, a)
},
{
case x # Book(t: String, a: String) =>
JObject(JField("title", JString(t)) ::
JField("author", JString(a)) :: Nil)
case Book(null, a: String) =>
JObject(JField("author", JString(a)) :: Nil) // `title` == null
}
))
val bookJValue = Extraction.decompose(Book(null, "Arthur C. Clark"))
val compacted = compact(render(bookJValue))
Output:
compacted: String = {"author":"Arthur C. Clark"}
You can find additional information on the page of json4s project.

How to add an additional json item per object in scala

I'm writing a simple scala application that opens a flat file of json data, parses it and finally prints it out to the screen.
The next step will require that I stop at each object and add another item (string) to the front of it. My question is how can I add a new string per object in this list?
The following is my JSON implementation (credit goes to the init author here)
import scala.util.parsing.combinator._
class JSON extends JavaTokenParsers {
def obj: Parser[Map[String, Any]] =
"{"~> repsep(member, ",") <~"}" ^^ (Map() ++ _)
def arr: Parser[List[Any]] =
"["~> repsep(value, ",") <~"]"
def member: Parser[(String, Any)] =
stringLiteral~":"~value ^^
{ case name~":"~value => (name, value) }
def value: Parser[Any] = (
obj
| arr
| stringLiteral
| floatingPointNumber ^^ (_.toInt)
| "null" ^^ (x => null)
| "true" ^^ (x => true)
| "false" ^^ (x => false)
)
}
Next I call this w/ a flat file like so
import java.io.FileReader
import scala23.JSON
class JSONTest extends JSON {
def main(args: String) {
val reader = new FileReader(args)
println(parseAll(value, reader))
}
}
Then I get a valid println of the json contents. Instead I would like to pass this parse method a String and have it append it or create a new json object that has the string at the front of each object inside
Update
My current attempt looks something like the below
class JSONTest extends JSON {
def main(args: String) {
val reader = new FileReader(args)
val header = ("abc", "def")
// println(parseAll(value, reader).map(addHeader(_, header)))
println(parseAll(value, reader).map(addHeader(_.asInstanceOf[Map[String, Any]], header)))
}
def addHeader(xyz:Map[String, Any], header:(String, Any)):Map[String, Any] = {
xyz.map {
case (k, m:Map[String, Any]) => (k, addHeader(m))
case e => e
} + header
}
}
But I'm currently getting a few errors in Intellij
error: missing parameter type for expanded function ((x$1) => x$1.asInstanceOf[Map[String, Any]])
println(parseAll(value, reader).map(addHeader(_.asInstanceOf[Map[String, Any]], header)))
AND
error: not enough arguments for method addHeader: (xyz: Map[String,Any],header: (String, Any))Map[String,Any].
Unspecified value parameter header.
case (k, m:Map[String, Any]) => (k, addHeader(m))
Any help would be much appreciated (thank you in advance!)
Have you tried using map on the parser output instead.
Edit: this compiles on my machine
import java.io.FileReader
import scala23.JSON
class JSONTest extends JSON {
def main(args: String) {
val reader = new FileReader(args)
val header = ("abc", "def")
// println(parseAll(value, reader).map(addHeader(_, header)))
println(parseAll(value, reader).map(addHeader(_, header)))
}
def addHeader(xyz:Any, header:(String, Any)):Any = xyz match {
case obj:Map[String, Any] => obj.map {
case (k, m:Map[String, Any]) => (k, addHeader(m, header))
case e => e
} + header
case arr:List[Any] => arr.map(addHeader(_, header))
case e => e
}
}
It should be handling the varied output of the parse better.

How can I obtain Function objects from methods in Scala?

Suppose I have a simple class in Scala:
class Simple {
def doit(a: String): Int = 42
}
How can I store in a val the Function2[Simple, String, Int] that takes two arguments (the target Simple object, the String argument), and can call doit() get me the result back?
val f: Function2[Simple, String, Int] = _.doit(_)
same as sepp2k, just using another syntax
val f = (s:Simple, str:String) => s.doit(str)
For those among you that don't enjoy typing types:
scala> val f = (_: Simple).doit _
f: (Simple) => (String) => Int = <function1>
Following a method by _ works for for any arity:
scala> trait Complex {
| def doit(a: String, b: Int): Boolean
| }
defined trait Complex
scala> val f = (_: Complex).doit _
f: (Complex) => (String, Int) => Boolean = <function1>
This is covered by a combination of §6.23 "Placeholder Syntax for Anonymous Functions" and §7.1 "Method Values" of the Scala Reference