I need to read certain enum values (apart from other regular stuff, including some optionally null values) from MySQL
database in Scala
. I'm using ScalikeJdbc
for querying MySQL
and parsing the result-set. I cannot find any mention of parsing enum types in it's docs (maybe I need to dig deeper).
But my target application needs to have both ways of loading data: database and JSON
files. Therefore I'm also using ScalaJson
(Play Framework
) to parse Json files for reading exactly the same data as I would from MySQL
database.
I'm aware that both ScalikeJdbc
and ScalaJson
support parsing data by using Scala
's case classes for automatic conversion.
My questions are:
ScalikeJdbc
support parsing Enum values like ScalaJson
?case class
be used for parsing data in both ScalikeJdbc
and ScalaJson
or do they need to be different? My case class
in question would contain parameters of custom types, which in turn are case class
es themselves. These case class
es accept parameters of following types apart from Enum values (basically, they don't contain parameter of exactly same types, but the level of complexity would be identical):
Int
, String
Option[Int]
, Option[String]
Option[Seq[ (Int, String, Option[Int], Option[String]) ]]
JSON
/ XML
files) or overhead of adding enums is too much to justify their benefits?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
.