Scala sealed abstract class with enum object matching against string - scala

I am a beginner in scala, I have a case class for the response I am getting as json payload
case class CallbackPayload(
automation_id: String,
business_key: String,
state: String #received , validating
)
I have a sealed abstract class
object ExecutionStatus {
sealed abstract class ExecutionState(status: String) {
override def toString: String = status
}
case object RECEIVED extends ExecutionState("received")
case object VALIDATING extends ExecutionState("validating")
}
Now based on the response payload state I want to do a match against the ExecutionStatus objects
Something like below
def callback(payload: CallbackPayload): Either[Throwable, Json] = {
payload.state match {
case VALIDATING => Right(Json.obj("status" -> Json.fromString("validating")))
.....
}
}
Now because of payload.state type string I am unable to . How to do that in scala.

It looks like you may want to match on the status string, like so (using a simplified model of your code):
object ExecutionStatus {
sealed abstract class ExecutionState(val status: String) {
override def toString: String = status
}
case object RECEIVED extends ExecutionState("received")
case object VALIDATING extends ExecutionState("validating")
}
def parse(state: String): Either[Throwable, Map[String, String]] = {
state match {
case ExecutionStatus.RECEIVED.status =>
Right(Map("status" -> "received"))
case ExecutionStatus.VALIDATING.status =>
Right(Map("status" -> "validating"))
case _ =>
Left(new IllegalArgumentException(s"unrecognized state $state"))
}
}
assert(parse("received") == Right(Map("status" -> "received")))
assert(parse("validating") == Right(Map("status" -> "validating")))
assert(parse("foo").isLeft)
You can play around with this code here on Scastie.
Notice that in order to do so I had to make status a val so that it's public (without it, status is only a constructor parameter).

Related

Return Instance of a case class dynamically with pattern matching

I have different case classes and objects for that case class, and I am trying to instantiate the class object and return that class.
case class KeySet(KeyID:String,KeyName:String,KeyType:String)
object KeySet{
def fromXml(node: scala.xml.Node):KeySet = {
//some operation
new KeySet(KeyID,KeyName,KeyType)
}
}
case class NodeSet(NodeID:String,NodeValue:String,NodeType:String,NodeKey:String,NodeLocation:String)
object NodeSet{
def fromXml(node: scala.xml.Node):NodeSet = {
//some operation
new KeySet(NodeID,NodeValue,NodeType,NodeKey,NodeLocation)
}
}
and I have a method to create an instance of a class and return class object.
def getConnectionDetails(connectionType:String) : Option[Class[_]]= {
connectionType match {
case "KeySet" => Some(() => {
val xml_cred= scala.xml.XML.loadString("xmlfile")
KeySet.fromXml(xml_cred)
})
case "NodeSet" => Some(() => {
val xml_cred= scala.xml.XML.loadString("xmlfile")
NodeSet.fromXml(xml_cred)
})
case _ => None
}
}
Here i am getting the error in Return type, each case will return different case class.
what would be the return type for this method?.
In this particular case, the only common ancestor between two case classes is AnyRef, hence the type which can be used as function result type. But, using AnyRef or Any is highly un-recommended practice, because of lost type safety.
Instead, what you need to dot is to form Sum Type, via using common sealed trait, like shown below:
sealed trait ConnectionDetails
case class KeySet(keyID: String, keyName: String, keyType: String) extends ConnectionDetails
case class NodeSet(nodeID: String, nodeValue: String, nodeType: String, nodeKey: String, nodeLocation: String) extends ConnectionDetails
def getConnectionDetails(connectionType:String) : Option[ConnectionDetails]= {
connectionType match {
case "KeySet" =>
val xml_cred= scala.xml.XML.loadString("xmlfile")
Some(KeySet.fromXml(xml_cred))
case "NodeSet" =>
val xml_cred= scala.xml.XML.loadString("xmlfile")
Some(NodeSet.fromXml(xml_cred))
case _ => None
}
}
So, in all other places you can do pattern match against ConnectionDetails and this will be safe.
Hope this helps!

Is there a way to implement a hierarchical enumeration in scala?

I am working in a project in which I need to tag the type of the received messages. Messages can come from different sources, but all these sources generate messages with the same conceptual type (so, the same meaning) but written in different ways.
For example from source1 I can receive
Source1:
{
"message_type": "typeA",
"value": 3
...
}
or
{
"message_type": "typeB",
"value": 3
...
}
But also from source2 I can receive
Source2:
{
"message_type": "A",
"value": 5
...
}
or
{
"message_type": "B",
"value": 2
...
}
I want to maximize the code reuse so I tried this solution.
The first scala file I created is a trait:
trait MessageType extends Enumeration {
val TYPE_A: Value
val TYPE_B: Value
}
then I implemented it in two object files:
object Source1MessageType extends MessageType{
override val TYPE_A: Value("typeA")
override val TYPE_B: Value("typeB")
object Source2MessageType extends MessageType{
override val TYPE_A: Value("A")
override val TYPE_B: Value("B")
So now what I want is to check the type of the message without knowing the source type, like this:
def foo(type: MessageType.Value) {
type match{
case MessageType.TYPE_A => ...do A action...
case MessageType.TYPE_B => ...do B action...
}
}
But if I write this code, the IDE (IntelliJ) highlights the parameter in red but it gives me no info about the error. It seems like I can use only Source1MessageType or Source2MessageType as parameter type.
I think the error is because Scala doesn't see the trait as an enumeration so I cannot access to the values of the enumeration.
Do you have any solution for that?
yes you can do hierarchical enumerations. So generally I would recommend against using Enumeration. Here's an article with why it's bad
https://medium.com/#yuriigorbylov/scala-enumerations-hell-5bdba2c1216
The most idiomatic way to do this is by leveraging sealed traits like this:
sealed trait MessageType{
def value:String
}
sealed trait MessageType1 extends MessageType
final case object TypeA extends MessageType1{
override def value:String = "typeA"
}
final case object TypeB extends MessageType1{
override def value:String = "typeB"
}
sealed trait MessageType2 extends MessageType
final case object A extends MessageType2{
override def value:String = "A"
}
final case object B extends MessageType2{
override def value:String = "B"
}
Note that all these definitions need to be in the same file. Now this works because sealed and final tells the compiler that inheritance can only happen in this file.
This means that given an instance of MessageType2 the compiler knows that it can only be either the object A or B it cannot be anything else (because of the sealed/final)
This gives you enums with exhaustiveness checks in patternmatching etc.
If "A" and "typeA" are names for the same message type then you need to deal with this when you read in the data. This means that you don't need any nesting in your enumeration.
trait MessageType
case object MessageTypeA extends MessageType
case object MessageTypeB extends MessageType
object MessageType {
def apply(s: String): MessageType =
s match {
case "A" | "typeA" => MessageTypeA
case "B" | "typeB" => MessageTypeB
case _ => throw BadMessageType
}
}
case class Message(msgType: MessageType, value: Int)
You can create the message type with MessageType(<string>) and it will return MessageTypeA or MessageTypeB as appropriate. You can use a normal match to determine which message type you have.
If you need to preserve the original string that generated the MessageType then you can store that as abstract value in the MessageType trait and fill it in when the appropriate instance is created:
trait MessageType {
def origin: String
}
case class MessageTypeA(origin: String) extends MessageType
case class MessageTypeB(origin: String) extends MessageType
object MessageType {
def apply(s: String): MessageType =
s match {
case "A" | "typeA" => MessageTypeA(s)
case "B" | "typeB" => MessageTypeB(s)
case _ => throw BadMessageType
}
}

Play Framework JSON Format for Case Objects

I have a set of case objects that inherits from a trait as below:
sealed trait UserRole
case object SuperAdmin extends UserRole
case object Admin extends UserRole
case object User extends UserRole
I want to serialize this as JSON and I just used the Format mechanism:
implicit val userRoleFormat: Format[UserRole] = Json.format[UserRole]
But unfortunately, the compiler is not happy and it says:
No unapply or unapplySeq function found
What is wrong in my case objects?
Ok I figured out what has to be done!
Here it is:
implicit object UserRoleWrites extends Writes[UserRole] {
def writes(role: UserRole) = role match {
case Admin => Json.toJson("Admin")
case SuperAdmin => Json.toJson("SuperAdmin")
case User => Json.toJson("User")
}
}
Another option is to override def toString like this:
File: Status.scala
package models
trait Status
case object Active extends Status {
override def toString: String = this.productPrefix
}
case object InActive extends Status {
override def toString: String = this.productPrefix
}
this.productPrefix will give you case object name
File: Answer.scala
package models
import play.api.libs.json._
case class Answer(
id: Int,
label: String,
status: Status
) {
implicit val answerWrites = new Writes[Answer] {
def writes(answer: Answer): JsObject = Json.obj(
"id" -> answer.id,
"label" -> answer.label,
"status" -> answer.status.toString
)
}
def toJson = {
Json.toJson(this)
}
}
File: Controller.scala
import models._
val jsonAnswer = Answer(1, "Blue", Active).toJson
println(jsonAnswer)
you get:
{"id":1,"label":"Blue","status":"Active"}
Hope this helps!

custom spray-json marshaller for a case class with Value type

case class HydraQueueMessage(tableType: HydraTableName.Value, payload: String)
object MedusaElementFormat extends DefaultJsonProtocol {
implicit object HydraElementFormat extends RootJsonFormat[HydraQueueMessage] {
def write(message: HydraQueueMessage) = JsObject(
MedusaEnum.HydraTableName.toString -> JsString(message.tableType.toString),
"payload" -> JsString(message.payload)
)
def read(value: JsValue) = {
value.asJsObject.getFields("tableType", "payload") match {
case Seq(JsString(tableType), JsString(payload)) =>
new HydraQueueMessage(tableType = tableType, payload = payload)
}
}
}
}
There's a type miss-match in this example, is there a cleaner way to achieve this? And still having the tableType as a Value and not String?
My marshaller throws a type miss-match with the Value type, I can't marshall JsValue either. So how would I proceed to marshall the HydraQueueMessage case class without using a String type for tableType?
You are trying to process too much at once. I would divide your problem in 2:
Process HydraTableName.Value
Process HydraQueueMessage
That will make things much easier.
To process your enum:
implicit object StepFormatJsonFormat extends RootJsonFormat[HydraTableName.Value] {
def write(obj: HydraTableName.Value): JsValue = JsString(obj.toString)
def read(jsonValue: JsValue): HydraTableName.Value = jsonValue match {
case JsString(stringObj) => HydraTableName.withName(stringObj)
case otherValue => throw new DeserializationException(s"HydraTableName.Value not found in ${otherValue.toString}")
}
}
Then the typical case class formatting:
implicit val HydraQueueMessageFormat = jsonFormat2(HydraQueueMessage.apply)
Probably the way to process the enum should be adapted.
If you include the HydraTableName, I can update the code.

spray-json serialization in spray-routing with custom JsonFormats

Using Spray with spray-json for a system, version:
"io.spray" %% "spray-json" % "1.2.6"
I cannot figure how to get custom JsonFormat definitions to work for serialization that is being handled by spray-routing.
I've had two separate circumstances that have failed.
1. Nested Case Classes
Basic case class JSON serialization has worked fine
case class Something(a: String, b: String)
implicit val something2Json = jsonFormat3(Something)
However if I have a nested case class in the case class to be serialized, I can resolve compile issues by providing another JsonFormat implicit, yet at run-time it refuses to serialize
case class Subrecord(value: String)
case class Record(a: String, b: String, subrecord: Subrecord)
object MyJsonProtocol extends DefaultJsonProtocol {
implicit object SubrecordJsonFormat extends JsonFormat[Subrecord] {
def write(sub: Subrecord) = JsString(sub.value)
def read(value: JsValue) = value match {
case JsString(s) => Subrecord(s)
case _ => throw new DeserializationException("Cannot parse Subrecord")
}
}
implicit val record2Json = jsonFormat3(Record)
}
This will throw a MappingException at runtime, explaining there is no usable value for subrecord
2. Trait with various 0-N case extensions
Here I have a trait that serves as a capturing type for a group of case classes. Some of the extending classes have vals while others have no vals and are objects. When serialization occurs, it seems like my implicit defined JsonFormat is completely ignored and I'm just give an empty JsObject, particularly when the actual underlying type was one of the case object's with no vals.
sealed trait Errors
sealed trait ErrorsWithReason extends Errors {
def reason: String
}
case class ValidationError(reason: String) extends ErrorsWithReason
case object EntityNotFound extends Errors
case class DatabaseError(reason: String) extends ErrorsWithReason
object MyJsonProtocol extends DefaultJsonProtocol {
implicit object ErrorsJsonFormat extends JsonFormat[Errors] {
def write(err: Errors) = failure match {
case e: ErrorsWithReason => JsString(e.reason)
case x => JsString(x.toString())
}
def read(value: JsValue) = {
value match {
//Really only intended to serialize to JSON for API responses
case _ => throw new DeserializationException("Can't reliably deserialize Error")
}
}
}
}
So given the above, if the actual type being serialized is EntityNotFound, then the serialization becomes a RootJsonFormat turning into {}. If it's an ErrorsWithReason then it becomes a RootJsonFormat turning into { "reason": "somevalue" }. I may be confused with how the JsonFormat definition is supposed to work, but it doesn't seem to be using my write method at all and instead has suddenly figured out how to serialize on its own.
EDIT
Specific serialization cases are using read/deserialization like:
entity(as[JObject]) { json =>
val extraction: A = json.extract[A]
}
And write/serialization with the complete directive.
I now am realizing thanks to the first answer posted here that my JsonDefaultProtocol and JsonFormat implementations are for spray-json classes, meanwhile the entity directive extraction in the deserialization is using json4s JObject as opposed to spray-json JsObject.
Another approach for clean JSON output
import spray.json._
import spray.json.DefaultJsonProtocol._
// #1. Subrecords
case class Subrecord(value: String)
case class Record(a: String, b: String, subrecord: Subrecord)
implicit object RecordFormat extends JsonFormat[Record] {
def write(obj: Record): JsValue = {
JsObject(
("a", JsString(obj.a)),
("b", JsString(obj.b)),
("reason", JsString(obj.subrecord.value))
)
}
def read(json: JsValue): Record = json match {
case JsObject(fields)
if fields.isDefinedAt("a") & fields.isDefinedAt("b") & fields.isDefinedAt("reason") =>
Record(fields("a").convertTo[String],
fields("b").convertTo[String],
Subrecord(fields("reason").convertTo[String])
)
case _ => deserializationError("Not a Record")
}
}
val record = Record("first", "other", Subrecord("some error message"))
val recordToJson = record.toJson
val recordFromJson = recordToJson.convertTo[Record]
println(recordToJson)
assert(recordFromJson == record)
If you need both reads and writes you can do it this way:
import spray.json._
import spray.json.DefaultJsonProtocol._
// #1. Subrecords
case class Subrecord(value: String)
case class Record(a: String, b: String, subrecord: Subrecord)
implicit val subrecordFormat = jsonFormat1(Subrecord)
implicit val recordFormat = jsonFormat3(Record)
val record = Record("a", "b", Subrecord("c"))
val recordToJson = record.toJson
val recordFromJson = recordToJson.convertTo[Record]
assert(recordFromJson == record)
// #2. Sealed traits
sealed trait Errors
sealed trait ErrorsWithReason extends Errors {
def reason: String
}
case class ValidationError(reason: String) extends ErrorsWithReason
case object EntityNotFound extends Errors
case class DatabaseError(reason: String) extends ErrorsWithReason
implicit object ErrorsJsonFormat extends JsonFormat[Errors] {
def write(err: Errors) = err match {
case ValidationError(reason) =>
JsObject(
("error", JsString("ValidationError")),
("reason", JsString(reason))
)
case DatabaseError(reason) =>
JsObject(
("error", JsString("DatabaseError")),
("reason", JsString(reason))
)
case EntityNotFound => JsString("EntityNotFound")
}
def read(value: JsValue) = value match {
case JsString("EntityNotFound") => EntityNotFound
case JsObject(fields) if fields("error") == JsString("ValidationError") =>
ValidationError(fields("reason").convertTo[String])
case JsObject(fields) if fields("error") == JsString("DatabaseError") =>
DatabaseError(fields("reason").convertTo[String])
}
}
val validationError: Errors = ValidationError("error")
val databaseError: Errors = DatabaseError("error")
val entityNotFound: Errors = EntityNotFound
assert(validationError.toJson.convertTo[Errors] == validationError)
assert(databaseError.toJson.convertTo[Errors] == databaseError)
assert(entityNotFound.toJson.convertTo[Errors] == entityNotFound)