akka http (un)marshall traits - scala

Let's assume the following Code:
sealed trait Action {
def run(): Boolean
}
case class SimpleAction(parameter: String) extends Actions {
// some impl
}
case class ExtendedAction(parameter1: String, parameter2: String) extends Actions {
// some impl
}
Now I want to define a webservice where one can retrieve the Actions. How can I Marshall the Action as it's just the trait and no specific Type?
I have found this https://github.com/spray/spray-json#providing-jsonformats-for-other-types in the Docs. Is there any simpler way to achieve this than using this approach mixed with pattern matching?

import spray.json._
import DefaultJsonProtocol._
implicit val simpleActionFormat = jsonFormat1(SimpleAction)
implicit val extendedActionFormat = jsonFormat2(ExtendedAction)
implicit val actionFormat1 = new JsonFormat[Action] {
override def write(obj: Action): JsValue = obj match {
case a: SimpleAction => JsObject("type" -> "simple".toJson, "value" -> a.toJson)
case b: ExtendedAction => JsObject("type" -> "extended".toJson, "value" -> b.toJson)
}
override def read(json: JsValue): Action = json.asJsObject.getFields("type", "value") match {
case Seq(JsString("simple"), js) => js.convertTo[SimpleAction]
case Seq(JsString("extended"), js) => js.convertTo[ExtendedAction]
case _ => throw new RuntimeException(s"Invalid json format: $json")
}
}
Or if you only care about converting Actions to json, then simply:
implicit val simpleActionFormat = jsonFormat1(SimpleAction)
implicit val extendedActionFormat = jsonFormat2(ExtendedAction)
implicit val actionFormat = lift(new JsonWriter[Action] {
override def write(obj: Action): JsValue = obj match {
case a: SimpleAction => a.toJson
case b: ExtendedAction => b.toJson
}
})

Related

getting serealizing case class with play json library

My case classes looks like this:
case class Person(personalInfo: PersonalInfo, bankInfo: BankInfo)
case class PersonalInfo(fname: String, lname: String)
case class BankInfo(atmCode: Int, creditCard: CreditCard)
case class CreditCard(number: Int, experationDate: String)
so to be able to get a person in my controller I added serealizer for person:
object PersonSerealizer {
implicit val PersonalInfoFormat: OFormat[PersonalInfo] = Json.format[PersonalInfo]
implicit val CreditCardFormat: OFormat[CreditCard] = Json.format[CreditCard]
implicit val BankInfoFormat: OFormat[BankInfo] = Json.format[BankInfo]
implicit val PersonFormat: OFormat[Person] = Json.format[Person]
}
in my controller I have a super simple action that looks like this:
i
mport serializers.PersonSerealizer._
def getBrothers(): Action[JsValue] = Action.async(parse.json) { request =>
request.body.validate[Person] match {
case JsSuccess(person, _) =>
brothersService.getBrothers(person) // this returns a List[Person]
.map(res => Future{Ok(res)})
case JsError(errors) => Future(BadRequest("Errors! " + errors.mkString))
}
}
but I get this error:
Error:(23, 81) No unapply or unapplySeq function found for class
BankInfo: / implicit val BankInfoFormat:
OFormat[BankInfo] = Json.format[BankInfo]
something is weird...from what I know it supposed to work
The order of defining your implicits seems to matter. Also, I think it is safer to use companion objects instead of defining them inside an arbitrary object.
case class PersonalInfo(fname: String, lname: String)
object PersonalInfo {
implicit val personalInfoJsonFormat = Json.format[PersonalInfo]
}
case class CreditCard(number: Int, experationDate: String)
object CreditCard {
implicit val creditCardJsonFormat = Json.format[CreditCard]
}
case class BankInfo(atmCode: Int, creditCard: CreditCard)
object BankInfo {
implicit val bankInfoJsonFormat = Json.format[BankInfo]
}
case class Person(personalInfo: PersonalInfo, bankInfo: BankInfo)
object Person {
implicit val personJsonFmt = Json.format[Person]
}
// Your action will be like this
def getBrothers() = Action.async(parse.json) { req =>
req.body.validate[Person] match {
case JsSuccess(p, _) =>
Future.successful(Ok(Json.toJson(service.getBrothers(p))))
case JsError(errors) =>
Future.successful(BadRequest(JsError.toJson(errors)))
}
}

Return Try or Future As Generic Container

Instead of using overloaded methods in object JSONSourceLoaderUtil, I want to switch to pattern matching style. How do I handle the resultant Try[JValue] and Future[JValue] as F[JValue]?
Imports and case classes,
import scalaz._
import Scalaz._
import org.json4s.JsonAST.{JObject, JValue}
trait DataSource
case class LocalFile(input: File) extends DataSource
case class RemoteResource(url: String, req: JValue) extends DataSource
What I have now,
object JSONSourceLoaderUtil {
def jsonFrom[F[_], S <: DataSource](source: S)(f: S => F[JValue])(implicit ev: Monad[F]): F[JValue] = ev.bind(ev.point(source))(f)
def extractFrom(source: RemoteResource): Future[JValue] = {
Future( ... ).flatMap(input => Future.fromTry(Parser.parseFromChannel(Channels.newChannel(input))))
}
def extractFrom(source: LocalFile): Try[JValue] = Parser.parseFromFile(source.input)
}
How do I convert to pattern matching style? Is there a another way to do this if I have painted myself into a corner? Thanks.
object JSONSourceLoaderUtil {
def jsonFrom[F[_], S <: DataSource](source: S)(f: S => F[JValue])(implicit ev: Monad[F]): F[JValue] = ev.bind(ev.point(source))(f)
def extractFrom(source: DataSource): F[JValue] = source match {
case RemoteResource(url, request) => Future( ... )
.flatMap(input => Future.fromTry(Parser.parseFromChannel(Channels.newChannel(input))))) // cannot convert Future to F
case LocalFile(input) => Parser.parseFromFile(input) // cannot convert Try to F
}
}
Your desired F depends on the type of the data source. So why not make this explicit?
trait DataSource[F[_]] {
def extract: F[JValue]
}
case class LocalFile(input: File) extends DataSource[Try] {
def extract = Parser.parseFromFile(input)
}
case class RemoteResource(url: String, req: JValue) extends DataSource[Future] {
def extract = Future( ... )
.flatMap(input => Future.fromTry(Parser.parseFromChannel(Channels.newChannel(input)))))
}
Removing the extract method and writing
def extractFrom[F[_]](source: DataSource[F]): F[JValue] = source match {
case RemoteResource(url, request) => Future( ... )
.flatMap(input => Future.fromTry(Parser.parseFromChannel(Channels.newChannel(input)))))
case LocalFile(input) => Parser.parseFromFile(input)
}
}
should also probably work, at least in Scala 2.12. But I find the first solution to be cleaner.

ReactiveMongo BSONHandler for Map

I have following class, and want to use reactivemongo.bson.Macros.handler to get the reader and writer automatically.
case class Data(a: String, b: Map[String, String])
object Data {
implicit val dataHandler = reactivemongo.bson.Macros.handler[Data]
}
But it fails to compile, saying Implicit Map[String,String] for 'value vs' not found
How to make this work? I'm using reactivemongo_2.10 0.11.10
According to this gist, I find a solution as:
case class Data(date: String, base: String, vs: Map[String, String])
object Data {
implicit object BSONMapHandler extends BSONHandler[BSONDocument, Map[String, String]] {
override def read(bson: BSONDocument): Map[String, String] = {
bson.elements.map {
case (key, value) => key -> value.asInstanceOf[BSONString].value
}.toMap
}
override def write(t: Map[String, String]): BSONDocument = {
val stream: Stream[Try[(String, BSONString)]] = t.map {
case (key, value) => Try((key, BSONString(value)))
}.toStream
BSONDocument(stream)
}
}
implicit val dataHandler = reactivemongo.bson.Macros.handler[Data]
}
Don't know why reactivemongo not support it by default.

Is there something like Map.keySet for a partial function in scala?

More specifically, I have:
case class Key (key: String)
abstract class abstr {
type MethodMap = PartialFunction[Key, String => Unit]
def myMap: MethodMap // abstract
def useIt (key: Key, value: String) = {
val meth = myMap(key)
meth(value)
}
def report = {
for (key <- myMap.keySet) // how to do this
println("I support "+key)
}
}
I use it like this:
class concrete extends abstr {
var one: Boolean
def method1(v: String): Unit = ???
def method2(v: String): Unit = ???
def map1: MethodMap = {
case Key("AAA") => method1
}
def map2: MethodMap = {
case Key("AAA") => method2
}
override def myMap: MethodMap = if (one) map1 else map2
}
Of course, this is somewhat simplified, but the report function is necessary.
Some history: I first had it implemented using Map but then I changed it to PartialFunction in order to support the following override def myMap: MethodMap = if (one) map1 else map2.
Any suggestion to refactor my code to support everything is also appreciated.
No. PartialFunction can be defined (and often is) on infinite sets. E.g. what do you expect report to return in these situations:
class concrete2 extends abstr {
def myMap = { case Key(_) => ??? }
}
or
class concrete2 extends abstr {
def myMap = { case Key(key) if key.length > 3 => ??? }
}
? If you have a finite list of values you are interested in, you can do
abstract class abstr {
type MethodMap = PartialFunction[Key, String => Unit]
def myMap: MethodMap // abstract
val keys: Seq[Key] = ...
def report = {
for (key <- keys if myMap.isDefined(key))
println("I support "+key)
}
}
Some history: I first had it implemented using Map but then I changed it to PartialFunction in order to support the last line in second part.
Why? This would work just as well with Map.
In your solution, is there any way to define the domain of the partial function to be the finite set keys
def f: MethodMap = { case key if keys.contains(key) => ... }
Of course, the domain isn't part of the type.

Play 2.2 match if there exists an implicit Json converter

I have put all my Json converters in one file JsonUtil, and then have a convertToJson method which tries to convert any object passed to json.
Basically a structure like this:
implicit val format_A = format[A]
implicit val format_B = format[B]
def convertToJson(o: Any): JsValue =
o match {
case a: A => Json.toJson(a)
case b: B => Json.toJson(b)
case a: Any => Logger.warn("could not convert to json: "+a); JsNull
}
but with alot more formatters / cases. I don't want to import all these implicits when I need conversions (for various reasons). Is there a way to match if there exists a valid toJson conversion, so I wouldn't have to write all the cases?
like:
case o: ExistJsonConversion => Json.toJson(o)
This works.
def convertToJson[A](a: A)(implicit ev: Format[A] = null): JsValue = {
if (ev != null) {
Json.toJson(a)(ev)
} else {
println("oops")
JsNull
}
}
A bit better version below (perhaps ;)
case class Perhaps[E](value: Option[E]) {
def fold[F](ifAbsent: => F)(ifPresent: E => F): F =
value.fold(ifAbsent)(ifPresent)
}
implicit def perhaps[E](implicit ev: E = null) = Perhaps(Option(ev))
def convertToJson[A](a: A)(implicit p: Perhaps[Format[A]]): JsValue = {
p.fold[JsValue] {
println("oops")
JsNull
} { implicit ev =>
Json.toJson(a)
}
}