I am trying to parse a complex json which has different schema for the same field in a same message. So I am trying to parse it via Either, but play json doesn't supports Either natively. So I tried to write custom reads and write function which has some syntax issue and quite tedious to figure out. Can some one help me with this?
Input json
{
"settings":[
{
"active":false,
"parameter":{
"x1":0,
"x2":"Simple"
},
"type":"sometype"
},
{
"active":true,
"parameter":[
{
"y1":100,
"y2":"east",
"y3":{
"blue":true,
"green":false
}
}
],
"type":"someDifferentType"
}
]
}
I have tried below code which has syntax problem
case class Xparameter(x1: Int, x2: String)
object Xparameter {
implicit val format: Format[Xparameter] = Json.format[Xparameter]
}
case class Yparameter(y1: Int, y2: String, y3: Color)
object Yparameter {
implicit val format: Format[Yparameter] = Json.format[Yparameter]
}
case class Color(blue: Boolean, green: Boolean)
object Color {
implicit val format: Format[Color] = Json.format[Color]
}
case class SettingLeft(
active: Boolean,
`type`: String,
parameter: Xparameter
)
object SettingLeft {
implicit val format: Format[SettingLeft] = Json.format[SettingLeft]
}
case class SettingRight(
active: Boolean,
`type`: String,
parameter: List[Yparameter]
)
object SettingRight {
implicit val format: Format[SettingRight] = Json.format[SettingRight]
}
case class Setting(
active: Boolean,
`type`: String,
parameter: Either[Xparameter, List[Yparameter]]
)
object Setting {
implicit def apply(active: Boolean,
`type`: String,
parameter: Xparameter
): Setting = Setting(active,`type`, Left(parameter))
implicit def apply(active: Boolean,
`type`: String,
parameter: List[Yparameter]
): Setting = Setting(active,`type`, Right(parameter))
implicit val format: Format[Setting] = new Format[Setting] {
override def reads(json: JsValue): JsResult[Setting] = json
.validate[SettingLeft]
.map(SettingLeft.apply) // <-- not sure if its possible to do
.orElse(
json
.validate[SettingRight]
.map(SettingRight.apply)
) // <--- syntax error
override def writes(json: Setting): JsValue = json.either match {
case Left(value) => Json.toJson(value)
case Right(value) => Json.toJson(value) // <--- syntax error
}
}
}
Related
I'm using Scala with Play framework (2.8.1) and have a Sort class for capturing sort based query string parameters. Sample url: http://myurl:9000?sortBy=name&sortOrder=asc. Here, the sortOrder field is optional and will have default value of "asc" if nothing is provided. I have implemented own QueryStringBindable class as below:
object Sort {
val asc = "asc"
implicit def queryStringBinder(implicit stringBinder: QueryStringBindable[String]) = new QueryStringBindable[Sort] {
override def bind(key: String, params: Map[String, Seq[String]]): Option[Either[String, Sort]] = {
for {
sortBy <- stringBinder.bind("sortBy", params)
if(params.contains("sortOrder")) {sortOrder <- stringBinder.bind("sortOrder", params)}
} yield {
(sortBy, sortOrder) match {
case (Right(sortBy), Right(sortOrder)) => Right(Sort(sortBy, Some(sortOrder)))
case _ => Left("Unable to bind Sort")
}
}
}
override def unbind(key: String, sort: Sort): String = {
stringBinder.unbind("sortBy", sort.sortBy) + "&" + stringBinder.unbind("sortOrder", sort.sortOrder.getOrElse(asc))
}
}
}
case class Sort(sortBy: String, sortOrder: Option[String] = Some(Sort.asc))
However, I'm unable to capture optional field sortOrder with default value if nothing is provided in the query string of url. I would want http://myurl:9000?sortBy=name to still sort by ascending order (default) even if &sortOrder isn't provided.
It might be easier not to use a for comprehension:
object Sort {
val asc = "asc"
implicit def queryStringBinder(implicit stringBinder: QueryStringBindable[String]) = new QueryStringBindable[Sort] {
override def bind(key: String, params: Map[String, Seq[String]]): Option[Either[String, Sort]] = {
val sortBy = stringBinder.bind("sortBy", params) // Option[Either[String, String]]
val sortOrder = stringBinder.bind("sortOrder", params) // Option[Either[String, String]]
val result = // Either[String, Sort]
(sortBy, sortOrder) match {
case (Some(Right(field)), Some(Right(order))) => Right(Sort(field, Some(order)))
case (Some(Right(field)), None) => Right(Sort(field))
case _ => Left("Unable to bind Sort")
}
Option(result)
}
override def unbind(key: String, sort: Sort): String = {
stringBinder.unbind("sortBy", sort.sortBy) + "&" + stringBinder.unbind("sortOrder", sort.sortOrder.getOrElse(asc))
}
}
}
case class Sort(sortBy: String, sortOrder: Option[String] = Some(Sort.asc))
I'm using spray-json and I need to parse the given request body (PATCH, POST), request body attributes can have following possibilities represented by Either[Unit.type, Option[A]]
value Not given Left[Unit.type]
value=null Null Right[None]
value=XXX Some value is provided Right[Some(value)]
Using the above possibilities I need to create a entity from the request body. While parsing I need to validate each field with some business logic (String length, integer range ...).
I have a following function for the business logic validation.
def validateValue[T](fieldName: String,
maybeValue: Try[T],
businessValidation: T => Boolean): Option[T] = {
maybeValue match {
case Success(value) if businessValidation(value) => Some(value)
case _ => None
}
}
Similarly another function readFieldWithValidation, here I will be parsing each attribute based on the input type and apply the business validation.
def readFieldWithValidation[S, T](fields: Map[String, JsValue], fieldName: String, businessValidation: T => Boolean)(
parse: S => T
): Option[T] = {
fields.get(fieldName) match {
case None => None
case Some(jsValue) =>
jsValue match {
case jsString: JsString =>
validateValue(fieldName, Try(parse(jsString.value)), businessValidation)
case JsNumber(jsNumber) =>
validateValue(fieldName, Try(parse(jsNumber.intValue)), businessValidation)
case _ => None
}
}
}
I have S ( Source ) and T ( Target ) which is used for given a JsValue returns T type. Here I only care about JsString and JsNumber.
The above lines of code is giving type mismatch error,
<console>:112: error: type mismatch;
found : jsString.value.type (with underlying type String)
required: S
validateValue(fieldName, Try(parse(jsString.value)), businessValidation)
^
<console>:114: error: type mismatch;
found : Int
required: S
validateValue(fieldName, Try(parse(jsNumber.intValue)), businessValidation)
Can someone help me how to overcome this error?
This is how I can use above function
val attributes = Map("String" -> JsString("ThisIsString"), "Int" -> JsNumber(23))
def stringLengthConstraint(min: Int, max: Int)(value: String) = value.length > min && value.length < max
readFieldWithValidation[JsString, String](attributes, "String", stringLengthConstraint(1, 10))(_.toString)
Your example is still not quite clear because it does not show the role of parse and actually looks contradictory to the other code: particularly you specify the generic parameter S as JsString in readFieldWithValidation[JsString, String] but given current (borken) readFieldWithValidation implementation your parse argument is probably expected to be of type String => String because jsString.value is String.
Anyway here is a piece of code that seem to implement something that is hopefully sufficiently close to what you want:
trait JsValueExtractor[T] {
def getValue(jsValue: JsValue): Option[T]
}
object JsValueExtractor {
implicit val decimalExtractor = new JsValueExtractor[BigDecimal] {
override def getValue(jsValue: JsValue) = jsValue match {
case JsNumber(jsNumber) => Some(jsNumber)
case _ => None
}
}
implicit val intExtractor = new JsValueExtractor[Int] {
override def getValue(jsValue: JsValue) = jsValue match {
case JsNumber(jsNumber) => Some(jsNumber.intValue)
case _ => None
}
}
implicit val doubleExtractor = new JsValueExtractor[Double] {
override def getValue(jsValue: JsValue) = jsValue match {
case JsNumber(jsNumber) => Some(jsNumber.doubleValue)
case _ => None
}
}
implicit val stringExtractor = new JsValueExtractor[String] {
override def getValue(jsValue: JsValue) = jsValue match {
case JsString(string) => Some(string)
case _ => None
}
}
}
def readFieldWithValidation[S, T](fields: Map[String, JsValue], fieldName: String, businessValidation: T => Boolean)(parse: S => T)(implicit valueExtractor: JsValueExtractor[S]) = {
fields.get(fieldName)
.flatMap(jsValue => valueExtractor.getValue(jsValue))
.flatMap(rawValue => Try(parse(rawValue)).toOption)
.filter(businessValidation)
}
and usage example:
def test(): Unit = {
val attributes = Map("String" -> JsString("ThisIsString"), "Int" -> JsNumber(23))
def stringLengthConstraint(min: Int, max: Int)(value: String) = value.length > min && value.length < max
val value = readFieldWithValidation[String, String](attributes, "String", stringLengthConstraint(1, 10))(identity)
println(value)
}
Your current code uses Option[T] as your return type. If I were using a code like this I'd probably added some error logging and/or handling for a case where the code contains a bug and attributes do contain a value for key fieldName but of some different, unexpected type (like JsNumber instead of JsString).
Update
It is not clear from your comment whether you are satisfied with my original answer or want to add some error handling. If you want to report the type mismatch errors, and since you are using cats, something like ValidatedNel is an obvious choice:
type ValidationResult[A] = ValidatedNel[String, A]
trait JsValueExtractor[T] {
def getValue(jsValue: JsValue, fieldName: String): ValidationResult[T]
}
object JsValueExtractor {
implicit val decimalExtractor = new JsValueExtractor[BigDecimal] {
override def getValue(jsValue: JsValue, fieldName: String): ValidationResult[BigDecimal] = jsValue match {
case JsNumber(jsNumber) => jsNumber.validNel
case _ => s"Field '$fieldName' is expected to be decimal".invalidNel
}
}
implicit val intExtractor = new JsValueExtractor[Int] {
override def getValue(jsValue: JsValue, fieldName: String): ValidationResult[Int] = jsValue match {
case JsNumber(jsNumber) => Try(jsNumber.toIntExact) match {
case scala.util.Success(intValue) => intValue.validNel
case scala.util.Failure(e) => s"Field $fieldName is expected to be int".invalidNel
}
case _ => s"Field '$fieldName' is expected to be int".invalidNel
}
}
implicit val doubleExtractor = new JsValueExtractor[Double] {
override def getValue(jsValue: JsValue, fieldName: String): ValidationResult[Double] = jsValue match {
case JsNumber(jsNumber) => jsNumber.doubleValue.validNel
case _ => s"Field '$fieldName' is expected to be double".invalidNel
}
}
implicit val stringExtractor = new JsValueExtractor[String] {
override def getValue(jsValue: JsValue, fieldName: String): ValidationResult[String] = jsValue match {
case JsString(string) => string.validNel
case _ => s"Field '$fieldName' is expected to be string".invalidNel
}
}
}
def readFieldWithValidation[S, T](fields: Map[String, JsValue], fieldName: String, businessValidation: T => Boolean)
(parse: S => T)(implicit valueExtractor: JsValueExtractor[S]): ValidationResult[T] = {
fields.get(fieldName) match {
case None => s"Field '$fieldName' is required".invalidNel
case Some(jsValue) => valueExtractor.getValue(jsValue, fieldName)
.andThen(rawValue => Try(parse(rawValue).validNel).getOrElse("".invalidNel))
.andThen(parsedValue => if (businessValidation(parsedValue)) parsedValue.validNel else s"Business validation for field '$fieldName' has failed".invalidNel)
}
}
And the test example remains the same. Probably in your real code you want to use something more specific than just String for errors but that's up to you.
I am trying to return Future[Vector[String]] from for comprehension but I am getting Future[Nothing] as my return type. How to convert Future[Nothing] return type to Future[Vector[String]]?
Here is the code snippet:
def findProjectId(projectName: String, userId: String): Future[Nothing] = {
for {
projectIds <- dBService.fetchProjectIdByProjectName(projectName) // projectIds is Vector[String]
pid <- projectIds
projectId <- dBService.filterProjectId(pid, userId) // ProjectId is Vector[String]
if projectId.nonEmpty
} yield {
projectId map {
projectid =>
dBService.fetchRoleId map { // fetchRoleId returns Future[Vector[String]]
case Vector(rid) => dBService.fetchCollaborator(projectid, rid) map { // fetchCollaborator returns Future[Vector[String]]
listOfcollabs =>
if (listOfcollabs.nonEmpty) {
listOfcollabs ++ Vector(userId)
}
else {
Vector(userId)
}
}
}
}
}
}
Signatures of dbService methods are:
def checkCollaboratorOrAdmin(userId: String, projectId: String, roleId: String): Future[Vector[String]] = {
dbComponent.db.run(checkAdminOrCollab(roleId))
}
def fetchRoleId: Future[Vector[String]] = {
dbComponent.db.run(fetchRole)
}
def fetchCollaborator(roleId: String, projectId: String): Future[Vector[String]] = {
dbComponent.db.run(fetchCollaborators(roleId, projectId))
}
def fetchProjectIdByProjectName(projectName: String) = {
dbComponent.db.run(fetchProjectId(projectName))
}
def filterProjectId(projectId: String, userId: String) = {
dbComponent.db.run(filterProjectIdByUserId(projectId, userId))
}
These methods in turn call:
def fetchRoleId(userId: String, projectId: String): SqlStreamingAction[Vector[String], String, Effect] = {
sql"""select distinct(role_id) from project_user_role where user_id=$userId and project_id=$projectId""".as[String]
}
def checkAdminOrCollab(roleId: String): SqlStreamingAction[Vector[String], String, Effect] = {
sql"""select role_id from roles where role_id=$roleId and role_name="collaborator" """.as[String]
}
def fetchRole(): SqlStreamingAction[Vector[String], String, Effect] = {
sql"""select role_id from roles where role_name="collaborator"""".as[String]
}
def fetchCollaborators(roleId: String, projectId: String): SqlStreamingAction[Vector[String], String, Effect] = {
sql"""select user_id from project_user_role where roleId=$roleId and project_id=$projectId""".as[String]
}
def fetchProjectId(projectName: String): SqlStreamingAction[Vector[String], String, Effect] = {
sql"""select project_id from projects where project_name=$projectName""".as[String]
}
def filterProjectIdByUserId(projectId: String, userId: String): SqlStreamingAction[Vector[String], String, Effect] = {
sql"""select project_id from project_user_role where project_id=$projectId and user_id=$userId""".as[String]
}
I'm guessing that the Future[Nothing] comes from IntelliJ hints, rather than the compiler itself. The compiler gives a couple of errors, the first coming from this line:
pid <- projectIds
This is the error I get:
Test.scala:47:13: type mismatch;
[error] found : scala.collection.immutable.Vector[Int]
[error] required: scala.concurrent.Future[?]
The problem is that the for expression is trying to build a value of type Future[_] using using map, flatMap and filter calls. (The collection type of a for is the collection type of the first expression in the for). flatMap on Future takes a Future and turns Future[Future[_]] into Future[_]. However you are giving it a Vector which is not supported.
I'm also unsure of the broader logic because you have two nested Vectors (projectIds and listOfCollabs) but no mechanism for flattening this into a single vector.
You probably want to look at using Future.traverse or Future.sequence to turn lists of Future into Future[List].
It would also make sense to break this down into some named functions to make the code more comprehensible and give a better chance of isolating the problem.
Update
This code will call the appropriate functions and return the results. The return type is Future[Vector[Vector[Vector[String]]]] because each dBService call returns Future[Vector[String]] so you get nested Vectors. It is not clear from the question how to flatten this into the result you want, but it should be straightforward. (The result of the last call is flattened by the case statement which is why there 4 dBService calls but only 3 nested Vectors )
def findProjectId(projectName: String, userId: String): Future[Vector[Vector[Vector[String]]]] = {
dBService.fetchProjectIdByProjectName(projectName).flatMap { projectIds =>
Future.traverse(projectIds) { pid =>
dBService.filterProjectId(pid, userId).flatMap { projectId =>
Future.traverse(projectId) { projectid =>
dBService.fetchRoleId.flatMap { // fetchRoleId returns Future[Vector[String]]
case Vector(rid) =>
dBService.fetchCollaborator(projectid, rid) map { // fetchCollaborator returns Future[Vector[String]]
_ ++ Vector(userId)
}
}
}
}
}
}
}
It may be that you can just take the first element of some of these Vectors which would simplify the code.
Future.traverse(collection)(f) is equivalent to Future.sequence(collection.map(f)) but gives better layout and may be more efficient.
I have a case class:
case class EvaluateAddress(addressFormat: String,
screeningAddressType: String,
value: Option[String])
This was working fine until I have a new use case where "value" parameter can be a class Object instead of String.
My initial implementation to handle this use case:
case class EvaluateAddress(addressFormat: String,
screeningAddressType: String,
addressId: Option[String],
addressValue: Option[MailingAddress]) {
def this(addressFormat: String, screeningAddressType: String, addressId: String) = {
this(addressFormat, screeningAddressType, Option(addressId), None)
}
def this(addressFormat: String, screeningAddressType: String, address: MailingAddress) = {
this(addressFormat, screeningAddressType, None, Option(address))
}
}
But because of some problem, I can not have four parameters in any constructor.
Is there a way I can create a class containing three parameters: ** addressFormat, screeningAddressType, value** and handle both the use cases?
Your code works fine, to use the other constructor's you just need to use the new keyword:
case class MailingAddress(i: Int)
case class EvaluateAddress(addressFormat: String, screeningAddressType: String, addressId: Option[String], addressValue: Option[MailingAddress]) {
def this(addressFormat: String, screeningAddressType: String, addressId: String) = {
this(addressFormat, screeningAddressType, Option(addressId), None)
}
def this(addressFormat: String, screeningAddressType: String, address: MailingAddress) = {
this(addressFormat, screeningAddressType, None, Option(address))
}
}
val e1 = EvaluateAddress("a", "b", None, None)
val e2 = new EvaluateAddress("a", "b", "c")
val e3 = new EvaluateAddress("a", "b", MailingAddress(0))
You can create an auxilliary ADT to wrap different types of values. Inside EvaluateAddress you can check the alternative that was provided with a match:
case class EvaluateAddress(addressFormat: String,
screeningAddressType: String,
value: Option[EvaluateAddress.Value]
) {
import EvaluateAddress._
def doEvaluation() = value match {
case Some(Value.AsId(id)) =>
case Some(Value.AsAddress(mailingAddress)) =>
case None =>
}
}
object EvaluateAddress {
sealed trait Value
object Value {
case class AsId(id: String) extends Value
case class AsAddress(address: MailingAddress) extends Value
}
}
It's then possible to also define some implicit conversions to automatically convert Strings and MailingAddresses into Values:
object EvaluateAddress {
sealed trait Value
object Value {
case class AsId(id: String) extends Value
case class AsAddress(address: MailingAddress) extends Value
implicit def idAsValue(id: String): Value = AsId(id)
implicit def addressAsValue(address: MailingAddress): Value = AsAddress(address)
}
def withRawValue[T](addressFormat: String,
screeningAddressType: String,
rawValue: Option[T])(implicit asValue: T => Value): EvaluateAddress =
{
EvaluateAddress(addressFormat, screeningAddressType, rawValue.map(asValue))
}
}
Some examples of using those implicit conversions:
scala> EvaluateAddress("a", "b", Some("c"))
res1: EvaluateAddress = EvaluateAddress(a,b,Some(AsId(c)))
scala> EvaluateAddress("a", "b", Some(MailingAddress("d")))
res2: EvaluateAddress = EvaluateAddress(a,b,Some(AsAddress(MailingAddress(d))))
scala> val id: Option[String] = Some("id")
id: Option[String] = Some(id)
scala> EvaluateAddress.withRawValue("a", "b", id)
res3: EvaluateAddress = EvaluateAddress(a,b,Some(AsId(id)))
Currently I have couple of methods that are very similar and I would like to merge them into 1 method. Here are the 2 methods
def toInt(attrType: String, attrValue: String): Int = {
attrType match {
case "N" => attrValue.toInt
case _ => -1
}
}
def toString(attrType: String, attrValue: String): String = {
attrType match {
case "S" => attrValue
case _ => ""
}
}
I am thinking there is an easier way to do this in Scala using generic?
You could do the following:
trait Converter[T] {
def convert(attrType: String, attrValue: String): T
}
object ConverterTest {
implicit object IntConverter extends Converter[Int] {
def convert(attrType: String, attrValue: String): Int = {
attrType match {
case "N" => attrValue.toInt
case _ => -1
}
}
}
implicit object StringConverter extends Converter[String] {
def convert(attrType: String, attrValue: String): String = {
attrType match {
case "S" => attrValue
case _ => ""
}
}
}
def to[T: Converter](attrType: String, attrValue: String): T = {
implicitly[Converter[T]].convert(attrType, attrValue)
}
def main(args: Array[String]) {
println(to[String]("S", "B"))
println(to[String]("N", "B"))
println(to[Int]("S", "23"))
println(to[Int]("N", "23"))
}
}
Its more code, and I couldn't get type inferencing to work, so it is probably of limited use.
But it is a single method plus a bunch of converters that can get controlled at the call site, so you get some extra flexibility.
Is it worth the effort? Depends on the actual use case.