I want to serialize and deserialize case classes to JSON, and after deserializing them, based on their subtype, I'd like to handle them differently. Something like this:
sealed trait Salutation
case class Hello(name: String)
case class Greetings(name: String)
case class HeyThere(name: String)
trait Handler[A] {
def greet(salutation: A)
}
implicit val helloSalutation: Handler[Hello] =
new Handler[Hello] {
def greet(salutation: Hello) = println(s"Hello, ${salutation.name}")
}
implicit val greetingsSalutation: Handler[Greetings] =
new Handler[Greetings] {
def greet(salutation: Greetings) = println("Greetings.")
}
implicit val heyThereSalutation: Handler[HeyThere] =
new Handler[HeyThere] {
def greet(salutation: HeyThere) = println("Hey there!")
}
val action = deserializeSalutationFromJson[Salutation](jsonStringFromSomewhere)
// NOTE: _action_ will be of the correct subtype, e.g. Greeting
/* TODO val handler = select which handler to use based the type of action */
handler.greet(action)
What is a good pattern in Scala for doing this of selection based on a dynamic type from JSON?
Related
I have a controller
def getCars(notation: Option[Boolean] = Some(false)) = identified.auth(hasOceanScope).async { implicit request =>
carService.getCars().map {
case Seq() => Response.NotFound
case cars => Response.Ok(cars)
}
}
Car case class looks like this:
case class Car(
name: String,
createdAt: LocalDateTimeOffset,
wheels: Seq[Wheel]
)
object Car{
implicit val wheelFormat = Wheel.format
implicit def toOffset(date: LocalDateTime): LocalDateTimeOffset = LocalDateTimeOffset.apply(date)
implicit val format = Json.format[Car]
case class Wheel(
name: String,
createdAt: LocalDateTimeOffset
)
object Wheel{
implicit val format = Json.format[Wheel]
implicit def toOffset(date: LocalDateTime): LocalDateTimeWithOffset = LocalDateTimeWithOffset.apply(date)
)
When notation query parameter is true -> want to return createdAt Car object and Wheel object field with notation => 2022-10-22T00:00:00#1
When notation query parameter is false -> want to return createdAt Car object and Wheel object field without notation => 2022-10-22T00:00:00
That is why I have create two formats in LocalDateTimeOffset object
case class LocalDateTimeWithOffset(dt: LocalDateTime, offset: Int) {
val localDateTimeWithOffsetReads: Reads[LocalDateTimeWithOffset] = Reads.of[String].flatMap {
str => if (str.contains("#")) {
val (dt, offset) = str.splitAt(str.indexOf("#"))
Reads.pure(LocalDateTimeWithOffset(LocalDateTime.parse(dt), offset.drop(1).toInt))
} else {
Reads.pure(LocalDateTimeWithOffset(LocalDateTime.parse(str), 1))
}
}
val localDateTimeWithOffsetWrites: Writes[LocalDateTimeWithOffset] = new Writes[LocalDateTimeWithOffset] {
override def writes(a: LocalDateTimeWithOffset): JsValue = JsString(a.dt.format(dateTimeUTCFormatter) + s"#${a.offset}")
}
val localDateTimeWithOffsetWritesOff: Writes[LocalDateTimeWithOffset] = new Writes[LocalDateTimeWithOffset] {
override def writes(a: LocalDateTimeWithOffset): JsValue = JsString(a.dt.format(dateTimeUTCFormatter))
}
val localDateTimeWithoutOffsetFormat: Format[LocalDateTimeWithOffset] = Format(localDateTimeWithOffsetReads, localDateTimeWithOffsetWritesOff)
val localDateTimeWithOffsetFormat: Format[LocalDateTimeWithOffset] = Format(localDateTimeWithOffsetReads, localDateTimeWithOffsetWrites)
implicit var format: Format[LocalDateTimeWithOffset] = localDateTimeWithoutOffsetFormat
}
But how can I use two different formats from controller based on notation query parameter value?
Well just looking at your question's title, changing implicit value is not something you would see Scala developers do, because compiler is responsible to lookup for implicit values, and you would definitely want to avoid ambiguous implicits found error. instead, you see developers using something so called type class instance constructor or something similar. This is how it works in your case:
Assuming you have a class A, which can be formatted to/from Json in many ways:
case class A(field1: String) // could have more fields
object A {
val formatFirstApproach: Format[A] = ???
val formatSecondApproach: Format[A] = ???
// note that the above instances are not implicit
def getFormat(somePredicate: Boolean): Format[A] = {
// input parameters can be anything, these are the parameters you need,
// in order to be able to decide which instance to return
if (somePredicate) formatFirstApproach else formatSecondApproach
}
}
And then, given a class B which has an instance variable of type A in it, you can use the type class instance constructor:
case class B(a: A, field2: Int)
object B {
// this is the type class instance constructor, since it constructs an instance of a type class (Format in this case)
implicit def format(implicit aFormatter: Format[A]): Format[B] = Json.format
}
And the thing is, you probably would not care about the serialization unless in the controller layer, so in the controller layer you can do:
def someApi(flag: Boolean) = Action async { req =>
implicit val aFormatter = A.getFormat(flag) // that's it, you don't need to mention anything more anywhere
businessLogic().map {
case Seq() => Response.NotFound
case cars => Response.Ok(Json.toJson(cars))
}
}
I have a lot of classes such as DataFrameFlow, TextFlow, RDDFlow. They all derive from base class Flow.
Now I want to write a function judgeFlow which can read from a path: String and return something representing exact Flow type from which I can create corresponding instance. The whole code seems like the following
def judgeFlow(path:String) = /*1*/ {
Flow.getStoreType(path) match {
case StoreType.tdw =>
DataFrameFlow
case StoreType.hdfs =>
TextFlow
}
}
def createFlow(typeInfo:/*2*/) = /*3*/{
new typeInfo()
}
However, I don't know how to write in place 1, 2 and 3.
EDIT
Knowing how to construct them is not enough here, because I also want the following:
pattern matching through typeInfo
some ways to do asInstanceOf
EDIT 2
Definition of Flow
abstract class Flow(var outputName: String) extends Serializable{
def this() = this("")
...
}
Definition of DataFrameFlow
class DataFrameFlow(d: DataFrame, path: String) extends Flow {
var data: DataFrame = d
def this(data: DataFrame) = this(data, "")
def this(path: String) = this(null, path)
def this() = this(null, "")
...
}
Pattern matching can't return different types from different cases. The type returned by pattern matching is the least upper bound of types returned in cases.
When someone wants to return different types, most probably he/she wants a type class.
sealed abstract class Flow
class DataFrameFlow extends Flow
class TextFlow extends Flow
class RDDFlow extends Flow
trait JudgeFlow[In] {
type Out <: Flow
def judgeFlow(in: In): Out
}
object JudgeFlow {
implicit val `case1`: JudgeFlow[???] { type Out = DataFrameFlow } = ???
implicit val `case2`: JudgeFlow[???] { type Out = TextFlow } = ???
implicit val `case3`: JudgeFlow[???] { type Out = RDDFlow } = ???
}
def judgeFlow[In](in: In)(implicit jf: JudgeFlow[In]): jf.Out = jf.judgeFlow(in)
But the trouble is that types are resolved at compile time. You seem to want to choose a case based on a value of string i.e. at runtime. So you can't return more specific types than just Flow at compile time.
flatMap with Shapeless yield FlatMapper not found
It's hard to guess your use case completely.
But using Scala reflection you can try
import scala.reflect.runtime.universe._
import scala.reflect.runtime.currentMirror
def judgeFlow(path:String): Type = {
Flow.getStoreType(path) match {
case StoreType.tdw =>
typeOf[DataFrameFlow]
case StoreType.hdfs =>
typeOf[TextFlow]
}
}
def createFlow(typeInfo: Type): Flow = {
val constructorSymbol = typeInfo.decl(termNames.CONSTRUCTOR).asMethod
val classSymbol = typeInfo.typeSymbol.asClass
val classMirror = currentMirror.reflectClass(classSymbol)
val constructorMirror = classMirror.reflectConstructor(constructorSymbol)
constructorMirror().asInstanceOf[Flow]
}
Following this answer shows how to bind an Enumeration to a form using a case class.
However in Play 2.7.3 this code fails with:
No Json serializer found for type jura.SearchRequest. Try to implement an implicit Writes or Format for this type.
When I implement the formatter:
object SearchRequest {
implicit val searchRequestFormat: OFormat[SearchRequest] = Json.format[SearchRequest]
}
I get
No instance of play.api.libs.json.Format is available for
scala.Enumeration.Value in the implicit scope
Should I be trying to write a formatter for the system scala.Enumeration type?
Or is there another way to implement a formatter when Enumerations are involved?
Test case here.
I use for any Enumeration this Library: enumeratum
With Dotty there will be great Enumerations, but until then I think moving to enumeratum is the best way handling Enumerations in Scala. See also Dotty - Enumerations.
As a bonus there is a play-json Extension, see Play JSON Extension.
With this your code would look like this:
import enumeratum.{ PlayJsonEnum, Enum, EnumEntry }
sealed trait SearchRequest extends EnumEntry
object SearchRequest extends Enum[SearchRequest] with PlayJsonEnum[SearchRequest] {
val values = findValues
case object SuperRequest extends SearchRequest
case object SimpleRequest extends SearchRequest
..
}
In essence PlayJsonEnum[SearchRequest] does all the work.
To write the enum as a string as cchantep says you can use Writes.enumNameWrites, we specifically use to read and write the ID. Therefore we have an EnumFormat in the package global for enums:
package object enums {
implicit def reads[E <: Enumeration](enum: E): Reads[E#Value] = new Reads[E#Value] {
def reads(json: JsValue): JsResult[E#Value] = json match {
case JsNumber(s) =>
try {
JsSuccess(enum.apply(s.toInt))
} catch {
case _: NoSuchElementException => JsError(s"Enumeration expected of type: '${enum.getClass}', but it does not appear to contain the value: '$s'")
}
case _ => JsError("Number value expected")
}
}
implicit def writes[E <: Enumeration]: Writes[E#Value] = new Writes[E#Value] {
def writes(v: E#Value): JsValue = JsNumber(v.id)
}
implicit def formatID[E <: Enumeration](enum: E): Format[E#Value] =
Format(reads(enum), writes)
def readsString[E <: Enumeration](enum: E): Reads[E#Value] = new Reads[E#Value] {
def reads(json: JsValue): JsResult[E#Value] = json match {
case JsString(s) => {
try {
JsSuccess(enum.withName(s))
} catch {
case _: NoSuchElementException => JsError(s"Enumeration expected of type: '${enum.getClass}', but it does not appear to contain the value: '$s'")
}
}
case _ => JsError("String value expected")
}
}
implicit def writesString[E <: Enumeration]: Writes[E#Value] = new Writes[E#Value] {
def writes(v: E#Value): JsValue = JsString(v.toString)
}
implicit def formatString[E <: Enumeration](enum: E): Format[E#Value] =
Format(readsString(enum), writesString)
}
And used:
object SearchRequest extends Enumeration(1) {
type SearchRequest = Value
val ONE /*1*/ , TWO /*2*/ , ETC /*n*/ = Value
implicit val searchRequestFormat: Format[SearchRequest] = formatID(SearchRequest)
}
Add the following to your Enumeration Object
implicit object MatchFilterTypeFormatter extends Formatter[MatchFilterType.Value] {
override val format = Some(("format.enum", Nil))
override def bind(key: String, data: Map[String, String]) = {
try {
Right(MatchFilterType.withName(data.get(key).head))
} catch {
case e:NoSuchElementException => Left(Seq(play.api.data.FormError(key, "Invalid MatchFilterType Enumeration")))
}
}
override def unbind(key: String, value: MatchFilterType.Value) = {
Map(key -> value.toString)
}
}
implicit val matchFilterTypeFormat = new Format[MatchFilterType.MatchFilterType] {
def reads(json: JsValue) = JsSuccess(MatchFilterType.withName(json.as[String]))
def writes(myEnum: MatchFilterType.MatchFilterType) = JsString(myEnum.toString)
}
And then the Formatter/Controller given in the question will work.
A working test case is here.
I'm trying to access an implicit parameter for a generic type. Scala is able to find the implicit just fine in the straightforward case by calling a method with an explicit generic type, such as with printGenericType[Person] below.
However, if I create a TypeTag[Person] and pass it to printGenericTypeGivenTypeTag, Scala is unable to find the implicit parameter.
case class Person(name: String)
case class Animal(age: Int)
implicit val p = Person("cory")
implicit val a = Animal(2)
def main(args: Array[String]): Unit = {
// Can find the implicit Person, prints "Hello Person(cory)"
printGenericType[Person]
// Can find the implicit Animal, prints "Hello Animal(2)"
printGenericType[Animal]
// See comment below
printNamedType("Person")
printNamedType("Animal")
}
def printGenericType[T](implicit t: T) = {
println(s"Hello $t")
}
def printGenericTypeGivenTypeTag[T](typeTag: TypeTag[T])(implicit t: T) = {
println(s"Hello $t")
}
def printNamedType[T](name: String) = {
val typetag: TypeTag[T] = getTypeTag[T](name)
// Cannot find the implicit of type T, compiler error
printGenericTypeGivenTypeTag(typetag)
}
def getTypeTag[T](name:String): TypeTag[T] = ... //Implementation irrelevant
If I understand correctly, Scala locates implicit parameters at compile time, so it makes sense that it can't find an implicit parameter for the generic type T at compile time.
However, I know that an implicit instance of T does exist. Is it possible to rewrite printGenericTypeGivenTypeTag in such a way as to find the implicit value for T? At runtime, the method has access to the actual type of T, so it seems it should be able to locate an implicit parameter of the same type that is in scope.
For the curious, the reasoning behind this is to avoid this:
name match {
case "Person" => printGenericType[Person]
case "Animal" => printGenericType[Animal]
}
To answer the question
You're not really wanting to pass the T implicitly, but rather the TypeTag, and not the T. Here's what I mean, and you're probably better off with an implicit value class.
implicit class GenericPrinter[T](val obj: T) extends AnyVal {
def printGenericType()(implicit tag: TypeTag[T]) = {
// do stuff with the tag
Console.println("Hello $obj")
}
}
val x: Person = Person(...)
x.printGenericType
Now solving the real problem
If you are trying to print case classes, I'd probably go down the implicit macro route for added convenience. It's really trivial to write up a macro that does this for us, e.g output a debug string based on all the constructor params of an arbitrary case class.
trait DeepPrinter[T <: Product with Serializable] {
/**
* Prints a deeply nested debug string for a given case class.
* This uses implicit macros to materialise the printer type class.
* In English, when we request for an implicit printer: DeepPrinter[T],
* the pre-defined compile time macro will generate this method for us
* based on the fields of the given case class.
*
* #param sep A separator to use to delimit the rows in a case class.
* #return A fully traced debug string so we can see how a case class looks like.
*/
def debugString(sep: String = "\n"): String
}
object DeepPrinter {
implicit def deepPrinter[T <: Product with Serializable] = macro DeepPrinterImpl.deepPrinterImpl[T]
}
And the macro is pretty trivial, looks kind of like this.
import language.experimental.macros
import scala.reflect.macros.blackbox
#macrocompat.bundle
class DeepPrinterImpl(val c: blackbox.Context) {
import c.universe._
object CaseField {
def unapply(symbol: TermSymbol): Option[(Name, Type)] = {
if (symbol.isVal && symbol.isCaseAccessor) {
Some(symbol.name -> symbol.typeSignature)
} else {
None
}
}
}
def fields(tpe: Type): Iterable[(Name, Type)] = {
tpe.decls.collect { case CaseField(nm, tpeSn) => nm -> tpeSn }
}
def materialize[T : c.WeakTypeTag]: c.Expr[DeepPrinter[T]] = {
val tpe = weakTypeOf[T]
val (names, types) = fields(tpe).unzip
// change the package name to the correct one here!
val tree = q"""
new com.bla.bla.DeepPrinter[$tpe] {
def debugString(sep: String = "\n") = Seq(..$names).mkString(sep)
}
"""
c.Expr[DeepPrinter[T]](tree)
}
}
I am trying to write a generic method f[T](id:String) that is something like this:
case class A(x:String)
case class B(y:String)
case class C(z:String)
def f[T](id:String): T = { /* equivalent to T(id) */ }
val result1:A = f[A]("123") // returns A("123")
val result2:B = f{B]("345") // returns B("345")
val result3:C = f[C]("567") // returns C("567")
Unfortunately I cannot figure out how to work with the type T inside the method, besides using reflection. By "working with the type T" i mean for example being able to do something like the following, which I know doesn't work (for illustration purposes only):
T match {
case A => A(id)
case B => B(id)
}
or simply invoke T(ID) to create a new object of whatever type T is.
I can of course break up this into three methods:
def f1(id:String): A = { A(id) }
def f2(id:String): B = { B(id) }
def f3(id:String): C = { C(id) }
val result1:A = f1("123") // returns A("123")
val result2:B = f2("345") // returns B("345")
val result3:C = f3("567") // returns C("567")
but I'm hoping there is a way to keep it as one generic method to avoid some ugly boilerplate code duplication, and still be nearl as fast as the tree method version.
If you do not want to use reflection (ClassTag or TypeTag), you could use a Factory type class to achieve the desired functionality (unless it defeats the purpose of your generic function by generating a lot of duplicated simple code ;)).
case class A(s: String)
case class B(s: String)
case class C(s: String)
trait Factory[T] extends ((String) => T) {
def apply(arg: String): T
}
object Factory {
implicit object AFactory extends Factory[A] {
override def apply(arg: String): A = A(arg)
}
implicit object BFactory extends Factory[B] {
override def apply(arg: String): B = B(arg)
}
implicit object CFactory extends Factory[C] {
override def apply(arg: String): C = C(arg)
}
}
def create[T : Factory](arg: String): T = implicitly[Factory[T]].apply(arg)
create[A]("foo") | -> res0: A = A(foo)