As told in comment on the question, I did away with reading Enums
directly from MySQL
database or JSON
. Obviously, those fields are now simple VARCHAR
s in the database and I moved the validation logic into my Scala
code. Since I'm performing validation separately, the case class
es that I'm using to hold data read from MySQL
or JSON
no longer have any Enum.Value
type fields.
I would say that it would've made a lot more sense to have Enums
directly in the database; but in lack of simple solution, I chose this workaround. It is still an open question and I will update the answer once I find the solution.
Coming to the other part of my question
Can the same case class be used for parsing data in both ScalikeJdbc
and ScalaJson or do they need to be different?
Yes
I created case class
es with companion object
s and I'm able to use them for automatic-conversion in both ScalikeJdbc
as well as ScalaJson
. I'm posting a complete case class
along with it's companion object
that serves this dual purpose.
Note: This code sample comes from a framework that aims to move MySQL
tables from one-location to another. [actual production-code]
Case-class:
case class Table(dbId: Option[Int] = None,
id: Option[Int] = None,
name: String,
shouldCopy: Option[Boolean] = None,
lastUpdated: Option[DateTime] = None
)
Companion-object:
object Table {
private def mapResultToTable(rs: WrappedResultSet): Table = Table(
dbId = rs.intOpt("db_id"),
id = rs.intOpt("id"),
name = rs.string("name"),
shouldCopy = rs.booleanOpt("should_copy"),
lastUpdated = rs.dateTimeOpt("last_updated").flatMap { zonedDateTime: ZonedDateTime =>
Some(DateTime.parse(zonedDateTime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)).plusMinutes(330))
}
)
def getTables(db: Db)(implicit session: DBSession): Seq[Table] = {
sql"""
SELECT
*
FROM
db_name.tables
WHERE
db_id = ${db.id} AND
should_copy = b'1'
ORDER BY
name ASC
""".
map(mapResultToTable).
list().
apply()
}
// for Play-Json [Scala-Json]
implicit val dateTimeJsReader = JodaReads.jodaDateReads("yyyyMMddHHmmss")
implicit val dateTimeWriter = JodaWrites.jodaDateWrites("dd/MM/yyyy HH:mm:ss")
implicit val reads = Json.reads[Table]
implicit val writes = Json.writes[Table]
}
Here's explanation for specific parts of the code:
def mapResultToTable(rs: WrappedResultSet)
This method reads the result-set of database query and builds an object
of the case class
def getTables(db: Db)
This method queries MySQL
database is using ScalikeJdbc
implicit val dateTimeJsReader = JodaReads.jodaDateReads("yyyyMMddHHmmss")
implicit val dateTimeJsReader = JodaReads.jodaDateReads("yyyyMMddHHmmss")
These are Joda
DateTime
read-write converters (used by ScalaJson
) for the Joda
DateTime
parameter of case class
implicit val reads = Json.reads[Table]
implicit val writes = Json.writes[Table]
These are read-write converters for the given case class
Finally, here's the code-snippet for reading data of this case class from Json file
val tableOpt: Option[Table] = try {
val rawJsonString: String = Source.fromFile("path/to/file/fileName.json").mkString
Some(Json.parse(rawJsonString).as[Table])
} catch {
case ex: Throwable =>
println(s"Exception while reading Json file: '$ex'")
None
}
One final piece of advice for noobs like me: Unless you are comfortable with ScalaJson
(and JSON
parsing, in general), avoid using the Json.parse(jsonString).asOpt[T]
method (the above snippet uses .as[T]
method) in order to overcome subtle mistakes like this.
EDIT-1
Does ScalikeJdbc support parsing Enum values like ScalaJson?
Getting Enum.Value
really has nothing to do with ScalikeJdbc
: once you have a String
, how difficult is it to convert to Enum.Value
?
Here's a small example:
// Enum
object CloudProvider extends Enumeration {
val AWS: Value = Value("aws")
val MicrosoftAzure: Value = Value("microsoft-azure")
val GoogleCloud: Value = Value("google-cloud")
}
// Case Class
case class SiteInfo(website: String, provider: CloudProvider.Value)
// Mapper method
def mapResult(rs: WrappedResultSet): SiteInfo = {
val website: String = rs.string("website")
val provider: CloudProvider.Value = CloudProvider.values.
find(_.toString == rs.string("provider")).
getOrElse(CloudProvider.AWS)
SiteInfo(website, provider)
}
It's difficult to understand why I couldn't see the answer when it was right there in front of me: (mapResult
method)
..in general, is it a good idea to use Enums at all..
Yes, Absolutely
The entire point of having an Enum
type is to restrict the range of values a variable can take. I can't see an easier way to enforce such rules other than an Enum
.
Also once you have the sufficient know-how of language / library that your'e using (I didn't know much Scala
& ScalikeJdbc
at that time), there's absolutely no overhead (from coding perspective) for using Enum
; in fact they make code much clearer. Needless to say even from performance perspective, using Enum
s is far better that using String
.