The experimental "Inception" feature in Play 2.1 (Json.format[...]) only works for case classes (see here). How can I write my custom format implicit for a trait. I have the following construct:
sealed trait Plan {
def id: String
def name: String
def apps: Int
def users: Int
def testruns: Int
def price: Int
def prio: Int
}
And the following case classes which are extending the trait Plan.
case class Start(
id: String = "start",
name: String = "Start",
apps: Int = 1,
users: Int = 1,
testruns: Int = 10,
price: Int = 99,
prio: Int = 30) extends Plan
case class Pro(
id: String = "pro",
name: String = "Pro",
apps: Int = 2,
users: Int = 5,
testruns: Int = 25,
price: Int = 299,
prio: Int = 20) extends Plan
case class Premium(
id: String = "premium",
name: String = "Premium",
apps: Int = -1,
users: Int = -1,
testruns: Int = -1,
price: Int = 799,
prio: Int = 10) extends Plan
Now I need to write my custom implicit format val in the Plan companion object. I tried:
object Plan {
implicit val planFormats = (
(__ \ "id").format[String] and
(__ \ "name").format[String] and
(__ \ "apps").format[Int] and
(__ \ "users").format[Int] and
(__ \ "testruns").format[Int] and
(__ \ "price").format[Int] and
(__ \ "prio").format[Int]
)(Plan.apply, unlift(Plan.unapply))
}
However, a trait hasn't an apply or unapply method. What is the correct way to provide an implicit val for the json serialization in Play 2.1?
You simply have to provide your own function that creates a new instance from the given values.
Essentially a companion object to the trait that acts as a factory.
object Plan {
def apply(id: String, name: String, ...) = id match {
case "pro" => new Pro(id, name, ...)
...
}
def unapply(p: Person): Option[(String, String, ...)] = ...
}
Why are you using Traits and implementing case class ?
Why not using instances of classes, such as:
case class Plan (
id: String,
name: String,
apps: Int,
users: Int,
testruns: Int,
price: Int,
prio: Int
)
val start = new Plan("start", "Start", 1, 1, 10, 99, 30)
val pro = new Plan("pro", "Pro", 2, 5, 25, 299, 20)
val premium = new Plan("premium", "Premium", -1, -1, -1, 799, 10)
and then, you can keep your Json formatter:
object Plan {
implicit val planFormats = (
(__ \ "id").format[String] and
(__ \ "name").format[String] and
(__ \ "apps").format[Int] and
(__ \ "users").format[Int] and
(__ \ "testruns").format[Int] and
(__ \ "price").format[Int] and
(__ \ "prio").format[Int]
)(Plan.apply, unlift(Plan.unapply))
}
Related
I am trying to send a multipart/formdata object to the backend that contains a Freelancer object and two image files that have to be stored on the server. The saving on disc part works, but saving the JSON string as a freelancer object is not working. I tried converting the string with Jackson objectmapper but I think I am doing something wrong. When I debug the application it crashes at mapper.readValue and goes straight to the catch().
I also tried to work with kotlinx.serializer, but the import just would not work so I switched to Jackson.
The Kotlin controller that takes in the request:
private val imageDirectory = System.getProperty("user.dir") + "/images/"
#PostMapping(consumes = ["multipart/form-data"])
fun saveUser(
#RequestParam("profileImage") profileImage: MultipartFile,
#RequestParam("fileIdImage") fileIdImage: MultipartFile,
#RequestParam("freelancer") freelancer: String,
): ResponseEntity<*> {
return try {
val mapper = ObjectMapper();
val freelancerJson: Freelancer = mapper.readValue(freelancer, Freelancer::class.java)
println(freelancerJson.aboutYou)
makeDirectoryIfNotExist(imageDirectory)
val profileImagePath: Path = Paths.get(imageDirectory, profileImage.originalFilename)
val idImagePath: Path = Paths.get(imageDirectory, fileIdImage.originalFilename)
Files.write(profileImagePath, profileImage.bytes);
Files.write(idImagePath, fileIdImage.bytes);
JsonResponse(HttpStatus.OK, "Saved freelancer} ").createResponseEntity()
} catch (e: Exception) {
JsonResponse(HttpStatus.INTERNAL_SERVER_ERROR, e.message.toString()).createResponseEntity()
}
}
The request from the front end using vue:
The console output of the formdata:
Freelancer model:
#Entity
data class Freelancer(
#Id
val id: Int,
//maps ID to freelancer primary key
#MapsId
#OneToOne(targetEntity = User::class)
#JoinColumn(name = "freelancer_id")
//ignores the freelancer id because it is already mapped to val id
#JsonIgnore
val freelancerId: User,
val firstName: String? = null,
val lastName: String? = null,
val dateOfBirth: Date? = null,
val kvk: String? = null,
val btw: String? = null,
val phone: String? = null,
val street: String? = null,
val zipcode: String? = null,
val city: String? = null,
val housenumber: Int? = 0,
val addition: String? = null,
val nameCardHolder: String? = null,
val iban: String? = null,
val referral: String? = null,
val specialism: String? = null,
val aboutYou: String? = null,
val motivation: String? = null,
val workExperience: Int? = null,
val driverLicense: Boolean? = null,
val ownTransport: Boolean? = null,
val identificationImage: String? = null,
val smallBusinessScheme: Boolean? = false,
val profileImage: String? = null,
val previousWorkedPlatform: String? = null,
val servicesAmount: Int? = null,
val previousWorkedRating: Int? = null,
val foundBy: String? = null,
val privacyStatement: Boolean? = null,
)
I solved it by adding ID to the Json object I was sending to the backend, because my model has a one-to-one relation with another model I had to include it when mapping the Json to the model.
I have a map in scala like this.
val someData = Some(Map(genderKey -> gender,agekey -> age))
How to get the output as:
val key= genderkey
val value= gender
val key2 = agekey (Dynamic variable name)
val value2= age (Dynamic variable name)
Like this
someData.map(_.map {
case (k,v) => s"$k = $v"
}
.mkString(" and \n"))
.foreach(result => println(result))
My Mongo DB abstract Dao is defined as follows
abstract class DbMongoDAO1[K, T <: Keyable[K]] (implicit val manifestT: Manifest[T], val manifestK: Manifest[K])
extends DbDAO[K, T]
with DbDAOExtensions[K, T]
with MongoConnection2
with JsonDbImplicits
{
val thisClass = manifestT.runtimeClass
val simpleName = thisClass.getSimpleName
lazy val collection = db.getCollection(s"${DbMongoDAO1.tablePrefix}$simpleName")
override def insertNew(r:T): Result[String,T] = {
val json: String = r.toJson.compactPrint
collection.insertOne(Document(json))
KO("Not Implemented")
}
}
I'm getting an error in the following line of code when converting a case class to JSON.
Error:(31, 26) value toJson is not a member of type parameter T
val json: String = r.toJson.compactPrint
val json: String = r.toJson.compactPrint
The trait JsonDbImplicits is as follows
trait JsonDbImplicits extends DefaultJsonProtocol
with SprayJsonSupport with JodaImplicits {
implicit val json_UserEmail:RootJsonFormat[UserEmail] = jsonFormat5(UserEmail)
implicit val json_UserProfile:RootJsonFormat[UserProfile] = jsonFormat13(UserProfile)
implicit val json_UserSession:RootJsonFormat[UserSession] = jsonFormat5(UserSession)
}
The case classes UserEmail and UserProfile are defined as follows
case class UserEmail
(
// it is the full email address
#Key("_id") id: String
, account_id: String
, active: Boolean = false
, ts_created: DateTime = now
, ts_updated: DateTime = now
) extends Keyable[String]
trait DbUserEmail extends DbMongoDAO1[String,UserEmail]
and
case class UserProfile
(
// id is the same as AccountId
#Key("_id") id: String = UUID.randomUUID().toString
, gender: Option[String] = None
, first_name: Option[String] = Some("")
, last_name: Option[String] = Some("")
, yob: Option[Int] = None
, kids: Option[Int] = None
, income: Option[Int] = None
, postcode: Option[String] = None
, location: Option[Boolean] = Some(true)
, opt_in: Option[Boolean] = Some(true)
, third_party: Option[Boolean] = Some(true)
, ts_created: DateTime = now
, ts_updated: DateTime = now
) extends Keyable[String]
trait DbUserProfile extends DbMongoDAO1[String,UserProfile]
What am I missing?
I have RDD[Row] :
|---itemId----|----Country-------|---Type----------|
| 11 | US | Movie |
| 11 | US | TV |
| 101 | France | Movie |
How to do GroupBy itemId so that I can save the result as List of json where each row is separate json object(each row in RDD) :
{"itemId" : 11,
"Country": {"US" :2 },"Type": {"Movie" :1 , "TV" : 1} },
{"itemId" : 101,
"Country": {"France" :1 },"Type": {"Movie" :1} }
RDD :
I tried :
import com.mapping.data.model.MappingUtils
import com.mapping.data.model.CountryInfo
val mappingPath = "s3://.../"
val input = sc.textFile(mappingPath)
The input is list of jsons where each line is json which I am mapping to the POJO class CountryInfo using MappingUtils which takes care of JSON parsing and conversion:
val MappingsList = input.map(x=> {
val countryInfo = MappingUtils.getCountryInfoString(x);
(countryInfo.getItemId(), countryInfo)
}).collectAsMap
MappingsList: scala.collection.Map[String,com.mapping.data.model.CountryInfo]
def showCountryInfo(x: Option[CountryInfo]) = x match {
case Some(s) => s
}
val events = sqlContext.sql( "select itemId EventList")
val itemList = events.map(row => {
val itemId = row.getAs[String](1);
val çountryInfo = showTitleInfo(MappingsList.get(itemId));
val country = if (countryInfo.getCountry() == 'unknown)' "US" else countryInfo.getCountry()
val type = countryInfo.getType()
Row(itemId, country, type)
})
Can some one let me know how can I achieve this ?
Thank You!
I can't afford the extra time to complete this, but can give you a start.
The idea is that you aggregate the RDD[Row] down into a single Map that represents your JSON structure. Aggregation is a fold that requires two function parameters:
seqOp How to fold a collection of elements into the target type
combOp How to merge two of the target types.
The tricky part comes in combOp while merging, as you need to accumulate the counts of values seen in the seqOp. I have left this as an exercise, as I have a plane to catch! Hopefully someone else can fill in the gaps if you have trouble.
case class Row(id: Int, country: String, tpe: String)
def foo: Unit = {
val rows: RDD[Row] = ???
def seqOp(acc: Map[Int, (Map[String, Int], Map[String, Int])], r: Row) = {
acc.get(r.id) match {
case None => acc.updated(r.id, (Map(r.country, 1), Map(r.tpe, 1)))
case Some((countries, types)) =>
val countries_ = countries.updated(r.country, countries.getOrElse(r.country, 0) + 1)
val types_ = types.updated(r.tpe, types.getOrElse(r.tpe, 0) + 1)
acc.updated(r.id, (countries_, types_))
}
}
val z = Map.empty[Int, (Map[String, Int], Map[String, Int])]
def combOp(l: Map[Int, (Map[String, Int], Map[String, Int])], r: Map[Int, (Map[String, Int], Map[String, Int])]) = {
l.foldLeft(z) { case (acc, (id, (countries, types))) =>
r.get(id) match {
case None => acc.updated(id, (countries, types))
case Some(otherCountries, otherTypes) =>
// todo - continue by merging countries with otherCountries
// and types with otherTypes, then update acc
}
}
}
val summaryMap = rows.aggregate(z) { seqOp, combOp }
I meet a problem while implementing Json Write for my case class that I use with squeryl and which extend KeyedEntity. It looks like this:
case class Order(fk_soc : Int, order_date: String, date_creation: Timestamp,fk_uther_author: Int, fk_statut : Int, tva : Option[Double], total_ht: Option[Double],total_ttc: Option[Double], note: Option[String]) extends KeyedEntity[Int] {
val id : Int = 0
val ref : String = ""
val date_modif : Option[Timestamp] = Some(new Timestamp(DateTime.now.getMillis))
}
And my Writes:
implicit val orderFormat : Writes[Order] = (
( __ \ 'fk_societe).write[Int] and
( __ \ 'order_date).write[String] and
( __ \ 'date_creation).write[Timestamp] and
( __ \ 'fk_user_author).write[Int] and
( __ \ 'fk_statut).write[Int] and
( __ \ 'tva).write[Option[Double]] and
( __ \ 'total_ht).write[Option[Double]] and
( __ \ 'total_ttc).write[Option[Double]] and
( __ \ 'note).write[Option[String]]
)(unlift(Order.unapply))
And it works fine except that i need include "id" field in my json but as it's in the body of my case class, it doesn't falls in to the generated value.
So if I add "id" field to my Writes:
implicit val orderFormat : Writes[Order] = (
( __ \ 'id).write[Int] and
( __ \ 'fk_societe).write[Int] and
.......
It doesn't work as expected... How implement this Writes to include id field? I need a custom apply/unapply method?
I stress that my case class is implemented so to works with squeryl KeyedEntity ( id in the body ...)
So I resolved it this way:
I have created another case class that represents my data as I need:
First was :
case class Order(fk_soc : Int, order_date: String, date_creation: Timestamp,fk_uther_author: Int, fk_statut : Int, tva : Option[Double], total_ht: Option[Double],total_ttc: Option[Double], note: Option[String]) extends KeyedEntity[Int] {
val id : Int = 0
val ref : String = ""
val date_modif : Option[Timestamp] = Some(new Timestamp(DateTime.now.getMillis))
}
and the second :
case class OrderJ(id: Int, ref: String, date_modif : Option[Timestamp], fk_soc : Int, order_date: String, date_creation: Timestamp,
fk_uther_author: Int, fk_statut : Int, tva : Option[Double], total_ht: Option[Double],
total_ttc: Option[Double], note: Option[String])
I updated my Writes with 3 new fields... (id,ref,date_modif)
Then when I need to perform deserialisation first I transform Order object to OrderJ,
I use a function for this transformation:
def toOrderJ(o : Order): OrderJ = {
OrderJ(o.id,o.ref,o.date_modif, o.fk_soc, o.order_date, o.date_creation, o.fk_uther_author,
o.fk_statut,o.tva, o.total_ht, o.total_ttc, o.note)
}
And only then I make a call Json.toJson on my OrderJ object and it works as expected.
But truly I don't like this approach, It seems to me that there is a more elegant solution. I really hope on your suggestions and opinions.