Scala slick comparing operator dynamically - scala

I am developing a restful web service with play in scala and slick. I am trying to implement filtering. The only thing i cannot get working is to set the operator for the comparing operation dynamically. I want to support ===, =!=, <=, >= and LIKE.
I have a problem in the filteringOperator function with the generics but i cannot find it. The exception is as follows:
value === is not a member of HardwareRepo.this.dbConfig.driver.api.Rep[A]
Anybody an idea?
def filtering(exp: FilterExpression): (HardwareTable) => Rep[Option[Boolean]] = {
exp.field match {
case "serialNumber" => filteringOperator(exp, _.serialNumber, exp.value)
case "online" => filteringOperator(exp, _.online, Try(exp.value.toBoolean).getOrElse(false))
case _ => throw new FilterFieldNotSupportedException(s"Filter field '${exp.field}' not supported")
}
}
def filteringOperator[A](exp: FilterExpression, x: Rep[A], y: A): Rep[Option[Boolean]] = {
exp.operator match {
case FilterExpressionOperator.Equals => x === y
...
}
}
def all(offset: Int, size: Int, filter: List[FilterExpression]): Future[List[Hardware]] = {
var q = Hardwares.to[List]
//filtering
for (exp <- filter) {
try {
q = q.filter(filtering(exp))
} catch {
case ex: FilterFieldNotSupportedException => return Future.failed(ex)
}
}
//pagination
db.run(q.drop(offset).take(size).result)
}
whole sourcecode:
package models
import java.sql.Timestamp
import java.util.UUID
import javax.inject.Inject
import helper.{FilterExpression, FilterExpressionOperator, SortExpression, SortExpressionOrder}
import play.api.db.slick.DatabaseConfigProvider
import helper.exceptions.{EntityAlreadyExistsException, FilterFieldNotSupportedException, SortFieldNotSupportedException}
import slick.driver.JdbcProfile
import slick.lifted.ColumnOrdered
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.Try
case class Hardware(id: UUID,
createdAt: Timestamp,
serialNumber: String,
color: Int,
online: Boolean,
renterCount: Int)
class HardwareRepo #Inject()()(protected val dbConfigProvider: DatabaseConfigProvider) {
val dbConfig = dbConfigProvider.get[JdbcProfile]
val db = dbConfig.db
import dbConfig.driver.api._
private val Hardwares = TableQuery[HardwareTable]
val allowedSorting = Map( "id" -> { (hw: Hardware) => hw.id } )
private def _findById(id: UUID): DBIO[Option[Hardware]] =
Hardwares.filter(_.id === id).result.headOption
private def _findBySerialNumber(serialNumber: String): Query[HardwareTable, Hardware, List] =
Hardwares.filter(_.serialNumber === serialNumber).to[List]
def findById(id: UUID): Future[Option[Hardware]] =
db.run(_findById(id))
def findBySerialNumber(serialNumber: String): Future[List[Hardware]] =
db.run(_findBySerialNumber(serialNumber).result)
def sorting(exp: SortExpression): (HardwareTable) => ColumnOrdered[_] = {
exp.field match {
case "id" => if (exp.order == SortExpressionOrder.Asc) _.id.asc else _.id.desc
case "serialNumber" => if (exp.order == SortExpressionOrder.Asc) _.serialNumber.asc else _.serialNumber.desc
case "createdAt" => if (exp.order == SortExpressionOrder.Asc) _.createdAt.asc else _.createdAt.desc
case "color" => if (exp.order == SortExpressionOrder.Asc) _.color.asc else _.color.desc
case "online" => if (exp.order == SortExpressionOrder.Asc) _.online.asc else _.online.desc
case _ => throw new SortFieldNotSupportedException(s"Sort field '${exp.field}' not supported")
}
}
def filtering(exp: FilterExpression): (HardwareTable) => Rep[Option[Boolean]] = {
exp.field match {
case "serialNumber" => _.serialNumber === exp.value
case "online" => _.online === Try(exp.value.toBoolean).getOrElse(false)
case _ => throw new FilterFieldNotSupportedException(s"Filter field '${exp.field}' not supported")
}
}
def filteringOperator[A](exp: FilterExpression, x: Rep[A], y: A): Rep[Option[Boolean]] = {
exp.operator match {
case FilterExpressionOperator.Equals => x === y
}
}
def all(offset: Int, size: Int, sort: List[SortExpression], filter: List[FilterExpression]): Future[List[Hardware]] = {
var q = Hardwares.to[List]
//sorting
for (exp <- sort) {
try {
q = q.sortBy(sorting(exp))
} catch {
case ex: SortFieldNotSupportedException => return Future.failed(ex)
}
}
//filtering
for (exp <- filter) {
try {
q = q.filter(filtering(exp))
} catch {
case ex: FilterFieldNotSupportedException => return Future.failed(ex)
}
}
//pagination
db.run(q.drop(offset).take(size).result)
}
def exists(serialNumber: String): Future[Boolean] = {
db.run(Hardwares.filter(hw => hw.serialNumber === serialNumber).exists.result)
}
def create(serialNumber: Option[String]): Future[UUID] = {
if (serialNumber.isEmpty) {
db.run(Hardwares.map(hw => (hw.color)) returning Hardwares.map(_.id) += (0))
} else {
//check serial number
val action = (Hardwares.filter(hw => hw.serialNumber === serialNumber).exists.result.flatMap {
case true => DBIO.failed(new EntityAlreadyExistsException("Serialnumber already exists"))
case false => Hardwares.map(hw => (hw.color, hw.serialNumber)) returning Hardwares.map(_.id) += (0, serialNumber.get)
}).transactionally
db.run(action)
}
}
class HardwareTable(tag: Tag) extends Table[Hardware](tag, "hardware") {
def id = column[UUID]("id", O.PrimaryKey)
def createdAt = column[Timestamp]("created_at")
def serialNumber = column[String]("serial_number")
def color = column[Int]("color")
def online = column[Boolean]("online")
def renterCount = column[Int]("renter_count")
def * = (id, createdAt, serialNumber, color, online, renterCount) <> (Hardware.tupled, Hardware.unapply)
def ? = (id.?, createdAt.?, serialNumber.?, color.?, online.?, renterCount.?).shaped.<>({ r => import r._; _1.map(_ => Hardware.tupled((_1.get, _2.get, _3.get, _4.get, _5.get, _6.get))) }, (_: Any) => throw new Exception("Inserting into ? projection not supported."))
}
}

to resolve the error
value === is not a member of
HardwareRepo.this.dbConfig.driver.api.Rep[A]
for the === operator to work the profile api must be imported and available in scope. I would refactor the class HardwareRepo as below
class HardwareRepo #Inject() (protected val dbConfigProvider: DatabaseConfigProvider) {
with HasDatabaseConfigProvider[JdbcProfile] {
import driver.api._
/*
other repo methods
*/
}

Related

Scala compilation fails with: Could not find implicit value for parameter W

I got a doobie query that doesn't want to compile:
package de.x.vmdbplanningvalues.impl.queries
import de.x.campaignplans.CampaignPlanId
import de.x.distributionbrands.DistributionBrandId
import de.x.vmdbplanningvalues._
import doobie._
object UpdateVmdbPlanningValuesQuery {
case class VmdbPlanningUpdate(
creditRatingRejections: Option[Double],
goodsCoupons: Option[Double],
customerDiscounts: Option[Double],
campaignPlanId: String,
distributionBrandId: String,
country: String,
categoryId: String,
lane: VmdbLane
)
def apply(
distributionBrand: DistributionBrandId,
country: String,
campaignPlanId: CampaignPlanId,
category: String,
updates: List[VmdbPlanningValuesForVmdbLane]
): ConnectionIO[Unit] = {
for {
_ <- updateQuery(updates.map(update =>
VmdbPlanningUpdate(
update.creditRatingRejections,
update.goodsCoupons,
update.customerDiscounts,
campaignPlanId.id,
distributionBrand.id,
country,
category,
update.lane
)
))
} yield ()
}
def updateQuery[T: Write](updates: List[VmdbPlanningUpdate]): ConnectionIO[Int] = {
val sql =
"""
UPDATE vmdb_planning_values as vmdb
SET vmdb.credit_rating_rejections = ?,
vmdb.goods_coupons = ?,
vmdb.customer_discounts = ?
FROM campaign_plan cp
WHERE cp.id = ?
AND vmdb.distribution_brand_id = ?
AND vmdb.country_id = ?
AND vmdb.year=DATE_PART('year', cp.start_date)
AND vmdb.quarter=DATE_PART('quarter', cp.start_date)
AND vmdb.category_id = ?
AND vmdb.lane = ?
"""
Update[VmdbPlanningUpdate](sql).updateMany(updates)
}
}
However it fails with the following error:
[error] /Users/johannesklauss/Documents/campaign-service/server/src/main/scala/de/x/vmdbplanningvalues/impl/queries/UpdateVmdbPlanningValuesQuery.scala:60:35: could not find implicit value for parameter W: doobie.Write[de.x.vmdbplanningvalues.impl.queries.UpdateVmdbPlanningValuesQuery.VmdbPlanningUpdate]
[error] Update[VmdbPlanningUpdate](sql).updateMany(updates)
[error]
I am not quite sure what the error message means, since I am still a bit fuzzy about implicits in Scala. Does anybody have an idea?
Edit: Added VmdbLane.scala:
package de.x.vmdbplanningvalues
import io.circe.Decoder.Result
import io.circe._
sealed trait VmdbLane {
override def toString: String = VmdbLane.toEnum(this)
}
object VmdbLane {
case object New extends VmdbLane
case object CarryOver extends VmdbLane
case object Sale extends VmdbLane
case object Sum extends VmdbLane
def toEnum(e: VmdbLane): String =
e match {
case New => "new"
case CarryOver => "carryOver"
case Sale => "sale"
case Sum => "sum"
}
def fromEnum(s: String): Option[VmdbLane] =
Option(s) collect {
case "new" => New
case "carryOver" => CarryOver
case "sale" => Sale
case "sum" => Sum
}
implicit val jsonFormat: Encoder[VmdbLane] with Decoder[VmdbLane] =
new Encoder[VmdbLane] with Decoder[VmdbLane] {
override def apply(a: VmdbLane): Json = Encoder.encodeString(toEnum(a))
override def apply(c: HCursor): Result[VmdbLane] =
c.value.asString.flatMap(s => fromEnum(s)) match {
case Some(a) => Right(a)
case None => Left(DecodingFailure("VmdbLane", c.history))
}
}
}
The problem was not related to Circe it is related to how the custom doobie type mapping works. If you include something like inside the VmdbLane object:
implicit val natGet: Get[VmdbLane] = Get[String].map(in => {
in match {
case "New" => New
case "CarryOver" => CarryOver
case "Sale" => Sale
case "Sum" => Sum
}
})
implicit val natPut: Put[VmdbLane] = Put[String].contramap {
case New => "New"
case CarryOver => "CarryOver"
case Sale => "Sale"
case Sum => "Sum"
}
The compiler should include the mapping an it should work.

How to convert List[zio.Task[List[A]]] to zio.Task[[List[A]]

I need to process a set of Ids and return the result as zio.Task[List[RelevantReadingRow]]
def getBaselinesForRequestIds(baseLineReqIds: Set[String]): Task[List[RelevantReadingRow]] =
dynamoConnection
.run(
table.getAll("baseline_req_id" in baseLineReqIds)
)
.flatMap(_.toList.separate match {
case (err :: _, _) => ZIO.fail(new Throwable(describe(err)))
case (Nil, relevantReadings) => ZIO.succeed(relevantReadings)
})
The code above works but I need to process them in batches of 25 element as mutch.
I have try this but then I get a List of zio.Task
def getBaselinesForRequestIds(baseLineReqIds: Set[String]): Task[List[RelevantReadingRow]] = {
val subSet = baseLineReqIds.grouped(25).toList
val res = for {
rows <- subSet.map(reqIds => dynamoConnection
.run(
table.getAll("baseline_req_id" in reqIds)
).flatMap(e => e.toList.separate match {
case (err :: _, _) => ZIO.fail(new Throwable(describe(err)))
case (Nil, relevantReadings) => ZIO.succeed(relevantReadings)
}))
} yield rows
res // this is List[zio.Task[List[RelevantReadingRow]]]
}
I dont know how to convert back to a zio.Task[List[RelevantReadingRow]]
any sugestion?
You can use ZIO.collectAll to convert List[Task] to Task[List], I thought it was ZIO.sequence..., maybe I'm getting confused with cats...
Following example works with Zio2
package sample
import zio._
object App extends ZIOAppDefault {
case class Result(value: Int) extends AnyVal
val data: List[Task[List[Result]]] = List(
Task { List(Result(1), Result(2)) },
Task { List(Result(3)) }
)
val flattenValues: Task[List[Result]] = for {
values <- ZIO.collectAll { data }
} yield values.flatten
val app = for {
values <- flattenValues
_ <- Console.putStrLn { values }
} yield()
def run = app
}
In particular for your sample ...
and assuming that 'separate' it's just an extension method to collect some errors (returns a tuple of error list and result list), and ignoring the method 'describe' to turn an err into a throwable
https://scastie.scala-lang.org/jgoday/XKxVP2ECSFOv4chgSFCckg/7
package sample
import zio._
object App extends ZIOAppDefault {
class DynamoMock {
def run: Task[List[RelevantReadingRow]] = Task {
List(
RelevantReadingRow(1),
RelevantReadingRow(2),
)
}
}
case class RelevantReadingRow(value: Int) extends AnyVal
implicit class ListSeparate(list: List[RelevantReadingRow]) {
def separate: (List[String], List[RelevantReadingRow]) =
(Nil, list)
}
def getBaselinesForRequestIds(baseLineReqIds: Set[String]): Task[List[RelevantReadingRow]] = {
val dynamoConnection = new DynamoMock()
val subSet = baseLineReqIds.grouped(25).toList
val res: List[Task[List[RelevantReadingRow]]] = for {
rows <- subSet.map(reqIds => dynamoConnection
.run.flatMap(e => e.toList.separate match {
case (err :: _, _) => ZIO.fail(new Throwable(err))
case (Nil, relevantReadings) => ZIO.succeed(relevantReadings)
}))
} yield rows
for {
rows <- ZIO.collectAll(res)
} yield rows.flatten
}
val app = for {
values <- getBaselinesForRequestIds(Set("id1", "id2"))
_ <- Console.putStrLn { values }
} yield()
def run = app
}
So, here is an alternative solution based on #jgoday answer
def getBaselinesForRequestIds(baseLineReqIds: Set[String]): Task[List[RelevantReadingRow]] =
for {
values <- ZIO.foreachPar(baseLineReqIds.grouped(25).toList) {
reqIds =>
dynamoConnection
.run(
table.getAll("baseline_req_id" in reqIds)
).flatMap(e => e.toList.separate match {
case (err :: _, _) => ZIO.fail(new Throwable(describe(err)))
case (Nil, relevantReadings) => ZIO.succeed(relevantReadings)
})
}
} yield values.flatten

Using existing methods in macro

Suppose i have some class with some methods
class Clz ... {
def someMethod: Map[String, Long] = ...
def id: Long = 0L
}
i'm need to reuse someMethod and overwrite id
i don't know why but it's throw Stackoverflow and also i'm need to do something with params/methods of Clz without returning the result
what i've tried:
object Macros {
def impl(c: whitebox.Context)(annottees: c.Tree*): c.Tree = {
import c.universe._
annottees match {
case (cls # q"$_ class $tpname[..$_] $_(...$paramss) extends { ..$_ } with ..$_ { $_ => ..$stats }") :: Nil =>
val someMethodM = stats
.filter(case q"$_ def $methodName: $_ = $_" => methodName.toString == "someMethod")
.map {
case q"$_ def $_: $_ = $res" => res
}
q"""
$cls
object ClzCompanion {
val someMethodsRef: Map[String, Int] = Map.apply(..$someMethodM)
..${
paramss
.flatten
.foreach {
case q"$_ val $nname: $tpt = $valuee" =>
// simply do something with valuee and nothing else
// "fire and forget"
}
}
..${
paramss
.flatten
.map {
case q"$_ val $nname: $tpt = $valuee" =>
// here logic, if this nname in someMethodsRef then
// someMethodsRef.find({ case (key, _) => key == nname) match {
// case Some(_) => "def $nname: ($tpt, Int) = ... // new method with body
// case None => do nothing...
}
}
}
"""
}
}
}
how i can overwrite the id method at Clz?
and why it throws StackOverflow??

How to print source code of "IF" condition in "THEN"

I would like to print Scala source code of IF condition while being in THEN section.
Example: IF{ 2 + 2 < 5 } THEN { println("I am in THEN because: " + sourceCodeOfCondition) }
Let's skip THEN section right now, the question is: how to get source code of block after IF?
I assume that IF should be a macro...
Note: this question is redefined version of Macro to access source code of function at runtime where I described that { val i = 5; List(1, 2, 3); true }.logValueImpl works for me (according to other question Macro to access source code text at runtime).
Off-the-cuff implementation since I only have a minute:
import scala.reflect.macros.Context
import scala.language.experimental.macros
case class Conditional(conditionCode: String, value: Boolean) {
def THEN(doIt: Unit) = macro Conditional.THEN_impl
}
object Conditional {
def sourceCodeOfCondition: String = ???
def IF(condition: Boolean) = macro IF_impl
def IF_impl(c: Context)(condition: c.Expr[Boolean]): c.Expr[Conditional] = {
import c.universe._
c.Expr(q"Conditional(${ show(condition.tree) }, $condition)")
}
def THEN_impl(c: Context)(doIt: c.Expr[Unit]): c.Expr[Unit] = {
import c.universe._
val rewriter = new Transformer {
override def transform(tree: Tree) = tree match {
case Select(_, TermName("sourceCodeOfCondition")) =>
c.typeCheck(q"${ c.prefix.tree }.conditionCode")
case other => super.transform(other)
}
}
c.Expr(q"if (${ c.prefix.tree }.value) ${ rewriter.transform(doIt.tree) }")
}
}
And then:
object Demo {
import Conditional._
val x = 1
def demo = IF { x + 5 < 10 } THEN { println(sourceCodeOfCondition) }
}
And finally:
scala> Demo.demo
Demo.this.x.+(5).<(10)
It's a desugared representation of the source, but off the top of my head I think that's the best you're going to get.
See my blog post here for some discussion of the technique.
As of 2.13, you can also do this by wrapping the expression, which means you don't have to define a custom if function:
implicit def debugIf[A]: DebugIf => Unit = { cond: DebugIf =>
logger.info(s"condition = {}, result = ${cond.result}", cond.code)
}
decorateIfs {
if (System.currentTimeMillis() % 2 == 0) {
println("decorateIfs: if block")
} else {
println("decorateIfs: else block")
}
}
with the macro implementation:
def decorateIfs[A: c.WeakTypeTag](a: c.Expr[A])(output: c.Expr[DebugIf => Unit]): c.Expr[A] = {
def isEmpty(tree: Trees#Tree): Boolean = {
tree match {
case Literal(Constant(())) =>
true
case other =>
false
}
}
c.Expr[A] {
a.tree match {
// https://docs.scala-lang.org/overviews/quasiquotes/expression-details.html#if
case q"if ($cond) $thenp else $elsep" =>
val condSource = extractRange(cond) getOrElse ""
val printThen = q"$output(DebugIf($condSource, true))"
val elseThen = q"$output(DebugIf($condSource, false))"
val thenTree = q"""{ $printThen; $thenp }"""
val elseTree = if (isEmpty(elsep)) elsep else q"""{ $elseThen; $elsep }"""
q"if ($cond) $thenTree else $elseTree"
case other =>
other
}
}
}
private def extractRange(t: Trees#Tree): Option[String] = {
val pos = t.pos
val source = pos.source.content
if (pos.isRange) Option(new String(source.drop(pos.start).take(pos.end - pos.start))) else None
}
case class DebugIf(code: String, result: Boolean)

How to get the class of Option[_] on a None with Reflection

I have this setup where I want to populate an object fields from an XML NodeSeq. A cutdown version of my setup is shown. It works well, but when I put None in testObj, I get a java.util.NoSuchElementException: None.get.
The problem I believe is that I cannot find a way to get the class of the Option[_] when it is None. I've been stuck on this for ages and I cannot see a way out. I feel there must be a way to branch and populate the object field when the original value is None, but how?
import xml.NodeSeq
object test {
case class TestObject(name: Option[String] = None, value: Option[Double] = None)
def main(args: Array[String]): Unit = {
val testObj = TestObject(Some("xxx"), Some(12.0))
// val testObj = TestObject(None, Some(12.0))
val nodeSeq = <test> <name> yyy </name> <value> 34.0 </value> </test>
val result = getObject[TestObject](nodeSeq, Option(testObj))
println("result = " + result) // result = Some(TestObject(Some(yyy),Some(34.0)))
}
def getObject[A](nodeSeq: NodeSeq, obj: Option[A]): Option[A] = {
if ((nodeSeq.isEmpty) || (!obj.isDefined)) None else {
for (field <- obj.get.getClass.getDeclaredFields) {
field.setAccessible(true)
val fieldValue = field.get(obj.get)
if (fieldValue == null || !fieldValue.isInstanceOf[Option[_]]) None
var newValue: Option[_] = None
fieldValue.asInstanceOf[Option[_]].get match { // <---- None.get is the error here
case x: Double => newValue = Some((nodeSeq \ field.getName).text.trim.toDouble)
case x: String => newValue = Some((nodeSeq \ field.getName).text.trim)
}
val decField = obj.get.getClass.getDeclaredField(field.getName)
decField.setAccessible(true)
decField.set(obj.get, newValue)
}
obj
}
}
}
I'm ignoring some other parts of this code that I don't like, however, you can't call .get on a None. You can call map and it will return what I assume you want. Here's the modified code:
def getObject[A](nodeSeq: NodeSeq, obj: Option[A]): Option[A] = {
if (nodeSeq.isEmpty || obj.isEmpty) {
None
}
else {
for (field <- obj.get.getClass.getDeclaredFields) {
field.setAccessible(true)
val fieldValue = field.get(obj.get)
if (fieldValue == null || !fieldValue.isInstanceOf[Option[_]]) None
val newValue = fieldValue.asInstanceOf[Option[_]].map(a => {
a match {
case x: Double => Some((nodeSeq \ field.getName).text.trim.toDouble)
case x: String => Some((nodeSeq \ field.getName).text.trim)
}
})
val decField = obj.get.getClass.getDeclaredField(field.getName)
decField.setAccessible(true)
decField.set(obj.get, newValue)
}
obj
}
}
In practice, I usually avoid calling .get on options as it results in bugs quite often.
UPDATE:
This version is a little more functional, though it still alters the obj that you send in:
def getObject[A](nodeSeq: NodeSeq, obj: Option[A]): Option[A] = {
obj.map(o => {
for {
field <- o.getClass.getDeclaredFields
_ = field.setAccessible(true)
fieldValue <- Option(field.get(o))
} yield {
val newValue = fieldValue match {
case Some(a) => {
a match {
case x: Double => Some((nodeSeq \ field.getName).text.trim.toDouble)
case x: String => Some((nodeSeq \ field.getName).text.trim)
}
}
case _ => None
}
val decField = o.getClass.getDeclaredField(field.getName)
decField.setAccessible(true)
decField.set(o, newValue)
}
o
})
}
Actually, this might be closer to what you want: check the type of the field instead.
import xml.NodeSeq
object test {
case class TestObject(name: Option[String] = None, value: Option[Double] = None)
def main(args: Array[String]): Unit = {
val testObj = TestObject(Some("xxx"), Some(12.0))
// val testObj = TestObject(None, Some(12.0))
val nodeSeq = <test> <name> yyy </name> <value> 34.0 </value> </test>
val result = getObject[TestObject](nodeSeq, Option(testObj))
println("result = " + result) // result = Some(TestObject(Some(yyy),Some(34.0)))
}
def getObject[A](nodeSeq: NodeSeq, obj: Option[A]): Option[A] = {
if ((nodeSeq.isEmpty) || (!obj.isDefined)) None else {
for (field <- obj.get.getClass.getDeclaredFields) {
field.setAccessible(true)
val newValue = field.getGenericType match {
case t: ParametrizedType if (t.getActualType == classOf[Option[_]]) =>
val paramType = t.getActualTypeArguments()(0)
if (paramType == classOf[java.lang.Double]) // since Double can't be a generic type parameter on JVM
// from your original code, but it probably needs to be modified
// to handle cases where field isn't in nodeSeq or not a number
Some((nodeSeq \ field.getName).text.trim.toDouble)
else if (paramType == classOf[String])
Some((nodeSeq \ field.getName).text.trim)
else None
case _ => None
}
if (newValue.isDefined)
field.set(obj.get, newValue)
}
obj
}
}
}
Version creating a new object instead (might need modification for your requirements):
def getObject[A](nodeSeq: NodeSeq, objClass: Class[A]): Option[A] = {
if (nodeSeq.isEmpty) None else {
try {
val fieldValues = objClass.getDeclaredFields.flatMap { field =>
field.setAccessible(true)
field.getGenericType match {
case t: ParametrizedType if (t.getActualType == classOf[Option[_]]) =>
val paramType = t.getActualTypeArguments()(0)
if (paramType == classOf[java.lang.Double])
Some(Some((nodeSeq \ field.getName).text.trim.toDouble))
else if (paramType == classOf[String])
Some(Some((nodeSeq \ field.getName).text.trim))
else None
case _ => None
}
}
// assumes only one constructor, otherwise get the desired one
val ctor = objClass.getConstructors()(0)
Some(ctor.newInstance(fieldValues))
} catch {
case _ => None
}
}
}