Related
first method
i wonder to use Accumulator to calculate num of "NULL" String in different columns, so i write Spark code as follows(the code is simplified), when i put some input in appData's map operation, i could see std output in spark web ui, the value of accumulator is increased, but when i want to get the final value in driver, the accumulators are always be zero, i'll appreciate it if you could do me a favor
val mapAC = collection.mutable.Map[String, LongAccumulator]()
for (ei <- eventList) {
val idNullCN = sc.longAccumulator(ei + "_idNullCN")
mapAC.put(ei + "_idNullCN", idNullCN)
val packNullCN = sc.longAccumulator(ei + "_packNullCN")
mapAC.put(ei + "_packNullCN", packNullCN)
val positionNullCN = sc.longAccumulator(ei + "_positionNullCN")
mapAC.put(ei + "_positionNullCN", positionNullCN)
}
val mapBC = sc.broadcast(mapAC)
val res = appData.map(d => {
val ei = d.eventId
val map = mapBC.value
if (d.id.toUpperCase == "NULL") map(ei + "_idNullCN").add(1)
if (d.pack.toUpperCase == "NULL") map(ei + "_packNullCN").add(1)
if (d.position.toUpperCase == "NULL") map(ei + "_positionNullCN").add(1)
ei
})
res.count()
mapBC.value.foreach(ac=>{
println(ac._1 + ": " + ac._2.value)
})
second method
i've tried another way to caculate the value by creating a map accumulator like this.
import java.util
import java.util.Collections
import org.apache.spark.util.AccumulatorV2
import scala.collection.JavaConversions._
class CountMapAccumulator extends AccumulatorV2[String, java.util.Map[String, Long]] {
private val _map = Collections.synchronizedMap(new util.HashMap[String, Long]())
override def isZero: Boolean = _map.isEmpty
override def copy(): CountMapAccumulator = {
val newAcc = new CountMapAccumulator
_map.synchronized {
newAcc._map.putAll(_map)
}
newAcc
}
override def reset(): Unit = _map.clear()
override def add(key: String): Unit = _map.synchronized{_map.put(key, _map.get(key) + 1L)}
override def merge(other: AccumulatorV2[String, java.util.Map[String, Long]]): Unit = other match {
case o: CountMapAccumulator => for ((k, v) <- o.value) {
val oldValue = _map.put(k, v)
if (oldValue != null) {
_map.put(k, oldValue.longValue() + v)
}
// println("merge key: "+k+" old val: "+oldValue+" new Value: "+v+" current val: "+_map.get(k))
}
case _ => throw new UnsupportedOperationException(
s"Cannot merge ${this.getClass.getName} with ${other.getClass.getName}")
}
override def value: util.Map[String, Long] = _map.synchronized {
java.util.Collections.unmodifiableMap(new util.HashMap[String, Long](_map))
}
def setValue(value: Map[String, Long]): Unit = {
val newValue = mapAsJavaMap(value)
_map.clear()
_map.putAll(newValue)
}
}
then i invoke it as follows
val tmpMap = collection.mutable.Map[String, Long]()
for (ei <- eventList) {
tmpMap.put(ei + "_idNullCN", 0L)
tmpMap.put(ei + "_packNullCN", 0L)
tmpMap.put(ei + "_positionNullCN", 0L)
}
val accumulator = new CountMapAccumulator
accumulator.setValue(collection.immutable.Map[String,Long](tmpMap.toSeq:_*))
sc.register(accumulator, "CustomAccumulator")
val res = appData.map(d => {
val ei = d.eventId
if (d.id.toUpperCase == "NULL") accumulator.add(ei + "_idNullCN")
if (d.pack.toUpperCase == "NULL") accumulator.add(ei + "_packNullCN")
if (d.position.toUpperCase == "NULL") accumulator.add(ei + "_positionNullCN")
if (d.modulePos.toUpperCase == "NULL") accumulator.add(ei + "_modulePosNullCN")
ei
})
res.count()
accumulator.value.foreach(println)
but the accumulator value is still zero either
second method correct
since the program ends correctly, i did not check the log, after i take a look, i found this ERROR
java.lang.UnsupportedOperationException: Cannot merge $line105198665522.$read$$iw$$iw$CountMapAccumulator with $line105198665522.$read$$iw$$iw$CountMapAccumulator so i change merge methd's pattern matching code like this
override def merge(other: AccumulatorV2[String, java.util.Map[String, Long]]): Unit = other match {
case o: AccumulatorV2[String, java.util.Map[String, Long]] => for ((k, v) <- o.value) {
val oldValue: java.lang.Long = _map.get(k)
if (oldValue != null) {
_map.put(k, oldValue.longValue() + v)
} else {
_map.put(k, v)
}
println(s"key: ${k} oldValue: ${oldValue} newValue: ${v} finalValue: ${_map.get(k)}")
}
case _ => throw new UnsupportedOperationException(
s"Cannot merge ${this.getClass.getName} with ${other.getClass.getName}")
}
after changed o's type, it works finally, but it still confused me what first way behaves.
in your custom accumulator you have mistake in merge function, look at correct:
val oldValue: java.lang.Long = _map.get(k)
if (oldValue != null) {
_map.put(k, oldValue.longValue() + v)
} else {
_map.put(k, v)
}
I try to simplify validation process for serving responses for HTTP requests in Spray (I use Slick for database access). Currently, I check in one query if I should go further to the following query or not (return error). This end up with nested pattern matching. Every validation case can return different error so I can not use any flatMap.
class LocationDao {
val db = DbProvider.db
// Database tables
val devices = Devices.devices
val locations = Locations.locations
val programs = Programs.programs
val accessTokens = AccessTokens.accessTokens
def loginDevice(deviceSerialNumber: String, login: String, password: String): Either[Error, LocationResponse] = {
try {
db withSession { implicit session =>
val deviceRowOption = devices.filter(d => d.serialNumber === deviceSerialNumber).map(d => (d.id, d.currentLocationId.?, d.serialNumber.?)).firstOption
deviceRowOption match {
case Some(deviceRow) => {
val locationRowOption = locations.filter(l => l.id === deviceRow._2.getOrElse(0L) && l.login === login && l.password === password).firstOption
locationRowOption match {
case Some(locationRow) => {
val programRowOption = programs.filter(p => p.id === locationRow.programId).firstOption
programRowOption match {
case Some(programRow) => {
val program = Program(programRow.name, programRow.logo, programRow.moneyLevel, programRow.pointsForLevel,
programRow.description, programRow.rules, programRow.dailyCustomerScansLimit)
val locationData = LocationData(program)
val locationResponse = LocationResponse("access_token", System.currentTimeMillis(), locationData)
Right(locationResponse)
}
case None => Left(ProgramNotExistError)
}
}
case None => Left(IncorrectLoginOrPasswordError)
}
}
case None => Left(DeviceNotExistError)
}
}
} catch {
case ex: SQLException =>
Left(DatabaseError)
}
}
}
What is a good way to simplify this? Maybe there is other approach..
Generally, you can use a for-comprehension to chain together a lot of the monadic structures you have here (including Try, Option, and Either) without nesting. For example:
for {
val1 <- Try("123".toInt)
} yield for {
val2 <- Some(val1).map(_ * 2)
val3 = Some(val2 - 55)
val4 <- val3
} yield val4 * 2
In your style, this might otherwise have looked like:
Try("123".toInt) match {
case Success(val1) => {
val val2 = Some(val1).map(_ * 2)
val2 match {
case Some(val2value) => {
val val3 = Some(val2value - 55)
val3 match {
case Some(val4) => Some(val4)
case None => None
}
}
case None => None
}
case f:Failure => None
}
}
You can define a helper method for Eiterising your control flow.
The benefit of doing this is that you will have great control and flexibility in your flow.
def eitherMe[ I, T ]( eitherIn: Either[ Error, Option[ I ] ],
err: () => Error,
block: ( I ) => Either[ Error, Option[ T ] ]
): Either[ Error, Option[ T ] ] = {
eitherIn match {
case Right( oi ) => oi match {
case Some( i ) => block( i )
case None => Left( err() )
}
case Left( e ) => Left( e )
}
}
def loginDevice(deviceSerialNumber: String, login: String, password: String): Either[Error, LocationResponse] = {
try {
db withSession { implicit session =>
val deviceRowOption = devices.filter(d => d.serialNumber === deviceSerialNumber).map(d => (d.id, d.currentLocationId.?, d.serialNumber.?)).firstOption
val locationRowEither = eitherMe(
Right( deviceRowOption ),
() => { DeviceNotExistError },
deviceRow => {
val locationRowOption = locations.filter(l => l.id === deviceRow._2.getOrElse(0L) && l.login === login && l.password === password).firstOption
Right( locationRowOption )
}
)
val programRowEither = eitherMe(
locationRowEither,
() => { IncorrectLoginOrPasswordError },
locationRow => {
val programRowOption = programs.filter(p => p.id === locationRow.programId).firstOption
Right( programRowOption )
}
)
val locationResponseEither = eitherMe(
programRowEither,
() => { ProgramNotExistError },
programRow => {
val program = Program(programRow.name, programRow.logo, programRow.moneyLevel, programRow.pointsForLevel,
programRow.description, programRow.rules, programRow.dailyCustomerScansLimit)
val locationData = LocationData(program)
val locationResponse = LocationResponse("access_token", System.currentTimeMillis(), locationData)
Right(locationResponse)
}
)
locationResponseEither
}
} catch {
case ex: SQLException =>
Left(DatabaseError)
}
}
For me, when I sometime can't avoid nested complexity, I would extract out section of the code that make sense together and make it into a new method and give it a meaningful name. This will both document the code and make it more readable, and reduce the complexity inside each individual method. And usually, once I have done that, I am able to see the flow better and might be able to refactor it to make more sense (after first writing the tests to cover the behavior I want).
e.g. for your code, you can do something like this:
class LocationDao {
val db = DbProvider.db
// Database tables
val devices = Devices.devices
val locations = Locations.locations
val programs = Programs.programs
val accessTokens = AccessTokens.accessTokens
def loginDevice(deviceSerialNumber: String, login: String, password: String): Either[Error, LocationResponse] = {
try {
db withSession { implicit session =>
checkDeviceRowOption(deviceSerialNumber, login, password)
}
} catch {
case ex: SQLException =>
Left(DatabaseError)
}
}
def checkDeviceRowOption(deviceSerialNumber: String, login: String, password: String): Either[Error, LocationResponse] = {
val deviceRowOption = devices.filter(d => d.serialNumber === deviceSerialNumber).map(d => (d.id, d.currentLocationId.?, d.serialNumber.?)).firstOption
deviceRowOption match {
case Some(deviceRow) => {
val locationRowOption = locations.filter(l => l.id === deviceRow._2.getOrElse(0L) && l.login === login && l.password === password).firstOption
locationRowOption match {
case Some(locationRow) => { checkProgramRowOption(locationRow) }
case None => Left(IncorrectLoginOrPasswordError)
}
}
case None => Left(DeviceNotExistError)
}
}
def checkProgramRowOption(locationRow: LocationRowType): Either[Error, LocationResponse] = {
val programRowOption = programs.filter(p => p.id === locationRow.programId).firstOption
programRowOption match {
case Some(programRow) => {
val program = Program(programRow.name, programRow.logo, programRow.moneyLevel, programRow.pointsForLevel,
programRow.description, programRow.rules, programRow.dailyCustomerScansLimit)
val locationData = LocationData(program)
val locationResponse = LocationResponse("access_token", System.currentTimeMillis(), locationData)
Right(locationResponse)
}
case None => Left(ProgramNotExistError)
}
}
}
Note that this is just an illustration and probably won't compile because I don't have your lib, but you should be able to tweak the code for it to compile.
Here are the codes:
def find(loginInfo: LoginInfo): Future[Option[UserProfile]] = {
val res = DB.withSession { implicit session =>
//if loginInfo.providerID == "waylens"
userProfileTable.filter(u => u.userName === loginInfo.providerKey).list
}
val size = res.size
if (size <= 1)
Future(res.headOption.map(userProfileRecordToUserProfile))
else
throw new Exception("db error")
}
def findByEmail(providerID: String, email: String): Future[Option[UserProfile]] = {
val res = DB.withSession { implicit session =>
//if loginInfo.providerID == "waylens"
userProfileTable.filter(u => u.email === email && u.status === 1).list
}
val size = res.size
if (size <= 1)
Future(res.headOption.map(userProfileRecordToUserProfile))
else
throw new Exception("db error")
}
def findByProfileID(profileID: Long): Future[Option[UserProfile]] = {
val res = DB.withSession { implicit session =>
userProfileTable.filter(u => u.id === profileID).list
}
val size = res.size
if (size <= 1)
Future(res.headOption.map(userProfileRecordToUserProfile))
else
throw new Exception("db error")
}
These codes appeared many times, which is really annoying
val size = res.size
if (size <= 1)
Future(res.headOption.map(userProfileRecordToUserProfile))
else
throw new Exception("db error")
Does anyone have ideas about refactoring this in Slick?
You can pretty easily use high order functions here, first let's get your queries out of the methods:
lazy val a = userProfileTable.filter(u => u.userName === loginInfo.providerKey)
lazy val b = userProfileTable.filter(u => u.email === email && u.status === 1)
lazy val c = userProfileTable.filter(u => u.id === profileID)
Note that I'm making this lazy because you don't have a session in scope yet.
Now let's create a function to execute this queries (you can make this even more generic using a type parameter):
def executeQuery(q: ⇒ : Query[UserProfiles, UserProfiles#TableElementType]): Int = {
db.withSession { implicit session ⇒
q.list
}
}
Then we need to check the length:
def listToUserProfile(list: List[UserProfile]): Future[Option[UserProfile]] = {
if (list.length <= 1)
Future(list.headOption.map(userProfileRecordToUserProfile))
else
throw new Exception("db error")
}
Now you could do something like:
listToUserProfile(executeQuery(a))
There may be some error because your code is not runnable and I couldn't compile it.
First create a generic method to execute the queries which either returns a future or an error using Either
def getElement[E, U, C[_]](query: Query[E, U, C]) = {
db.withSession { implicit session =>
query.list.headOption match {
case Some(ele) => Right(Future(ele))
case None => Left(new Exception("db error"))
}
}
}
Now in the code, you can simply do
def find(loginInfo: LoginInfo): Future[Option[UserProfile]] =
getElement(userProfileTable.filter(u => u.userName === loginInfo.providerKey)).right.map(userProfileRecordToUserProfile)
Similarly for others:
def findByEmail(loginInfo: LoginInfo): Future[Option[UserProfile]] =
getElement( userProfileTable.filter(u => u.email === email && u.status === 1))).right.map(userProfileRecordToUserProfile)
On a side note, you are not wrapping the future over the DB call but after you get the result. DB call will be major source of blocking and not the processing of the DB result. You should look into wrapping the DB call inside the future
I have multiple Option's. I want to check if they hold a value. If an Option is None, I want to reply to user about this. Else proceed.
This is what I have done:
val name:Option[String]
val email:Option[String]
val pass:Option[String]
val i = List(name,email,pass).find(x => x match{
case None => true
case _ => false
})
i match{
case Some(x) => Ok("Bad Request")
case None => {
//move forward
}
}
Above I can replace find with contains, but this is a very dirty way. How can I make it elegant and monadic?
Edit: I would also like to know what element was None.
Another way is as a for-comprehension:
val outcome = for {
nm <- name
em <- email
pwd <- pass
result = doSomething(nm, em, pwd) // where def doSomething(name: String, email: String, password: String): ResultType = ???
} yield (result)
This will generate outcome as a Some(result), which you can interrogate in various ways (all the methods available to the collections classes: map, filter, foreach, etc.). Eg:
outcome.map(Ok(result)).orElse(Ok("Bad Request"))
val ok = Seq(name, email, pass).forall(_.isDefined)
If you want to reuse the code, you can do
def allFieldValueProvided(fields: Option[_]*): Boolean = fields.forall(_.isDefined)
If you want to know all the missing values then you can find all missing values and if there is none, then you are good to go.
def findMissingValues(v: (String, Option[_])*) = v.collect {
case (name, None) => name
}
val missingValues = findMissingValues(("name1", option1), ("name2", option2), ...)
if(missingValues.isEmpty) {
Ok(...)
} else {
BadRequest("Missing values for " + missingValues.mkString(", ")))
}
val response = for {
n <- name
e <- email
p <- pass
} yield {
/* do something with n, e, p */
}
response getOrElse { /* bad request /* }
Or, with Scalaz:
val response = (name |#| email |#| pass) { (n, e, p) =>
/* do something with n, e, p */
}
response getOrElse { /* bad request /* }
if ((name :: email :: pass :: Nil) forall(!_.isEmpty)) {
} else {
// bad request
}
I think the most straightforward way would be this:
(name,email,pass) match {
case ((Some(name), Some(email), Some(pass)) => // proceed
case _ => // Bad request
}
A version with stone knives and bear skins:
import util._
object Test extends App {
val zero: Either[List[Int], Tuple3[String,String,String]] = Right((null,null,null))
def verify(fields: List[Option[String]]) = {
(zero /: fields.zipWithIndex) { (acc, v) => v match {
case (Some(s), i) => acc match {
case Left(_) => acc
case Right(t) =>
val u = i match {
case 0 => t copy (_1 = s)
case 1 => t copy (_2 = s)
case 2 => t copy (_3 = s)
}
Right(u)
}
case (None, i) =>
val fails = acc match {
case Left(f) => f
case Right(_) => Nil
}
Left(i :: fails)
}
}
}
def consume(name: String, email: String, pass: String) = Console println s"$name/$email/$pass"
def fail(is: List[Int]) = is map List("name","email","pass") foreach (Console println "Missing: " + _)
val name:Option[String] = Some("Bob")
val email:Option[String]= None
val pass:Option[String] = Some("boB")
val res = verify(List(name,email,pass))
res.fold(fail, (consume _).tupled)
val res2 = verify(List(name, Some("bob#bob.org"),pass))
res2.fold(fail, (consume _).tupled)
}
The same thing, using reflection to generalize the tuple copy.
The downside is that you must tell it what tuple to expect back. In this form, reflection is like one of those Stone Age advances that were so magical they trended on twitter for ten thousand years.
def verify[A <: Product](fields: List[Option[String]]) = {
import scala.reflect.runtime._
import universe._
val MaxTupleArity = 22
def tuple = {
require (fields.length <= MaxTupleArity)
val n = fields.length
val tupleN = typeOf[Tuple2[_,_]].typeSymbol.owner.typeSignature member TypeName(s"Tuple$n")
val init = tupleN.typeSignature member nme.CONSTRUCTOR
val ctor = currentMirror reflectClass tupleN.asClass reflectConstructor init.asMethod
val vs = Seq.fill(n)(null.asInstanceOf[String])
ctor(vs: _*).asInstanceOf[Product]
}
def zero: Either[List[Int], Product] = Right(tuple)
def nextProduct(p: Product, i: Int, s: String) = {
val im = currentMirror reflect p
val ts = im.symbol.typeSignature
val copy = (ts member TermName("copy")).asMethod
val args = copy.paramss.flatten map { x =>
val name = TermName(s"_$i")
if (x.name == name) s
else (im reflectMethod (ts member x.name).asMethod)()
}
(im reflectMethod copy)(args: _*).asInstanceOf[Product]
}
(zero /: fields.zipWithIndex) { (acc, v) => v match {
case (Some(s), i) => acc match {
case Left(_) => acc
case Right(t) => Right(nextProduct(t, i + 1, s))
}
case (None, i) =>
val fails = acc match {
case Left(f) => f
case Right(_) => Nil
}
Left(i :: fails)
}
}.asInstanceOf[Either[List[Int], A]]
}
def consume(name: String, email: String, pass: String) = Console println s"$name/$email/$pass"
def fail(is: List[Int]) = is map List("name","email","pass") foreach (Console println "Missing: " + _)
val name:Option[String] = Some("Bob")
val email:Option[String]= None
val pass:Option[String] = Some("boB")
type T3 = Tuple3[String,String,String]
val res = verify[T3](List(name,email,pass))
res.fold(fail, (consume _).tupled)
val res2 = verify[T3](List(name, Some("bob#bob.org"),pass))
res2.fold(fail, (consume _).tupled)
I know this doesn't scale well, but would this suffice?
(name, email, pass) match {
case (None, _, _) => "name"
case (_, None, _) => "email"
case (_, _, None) => "pass"
case _ => "Nothing to see here"
}
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
}
}
}