I wanted to serialize and deserialize some case classes and realized I was repeating code. Unfortunately I cannot figure out a way to keep things DRY. Hoping someone can provide some assistance. Below I will provide a sample problem that is not DRY.
Sample Problem
import org.json4s.jackson.Serialization
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import org.json4s.JsonAST.JString
import org.json4s.{CustomSerializer, DefaultFormats}
case class Bar(bar: String, date: ZonedDateTime)
case class Foo(foo: String)
trait JsonParser {
private case object ZDTSerializer extends CustomSerializer[ZonedDateTime](_ => (
{ case JString(s) => ZonedDateTime.parse(s) },
{ case zdt: ZonedDateTime => JString(zdt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"))) }
))
implicit val formats = DefaultFormats + ZDTSerializer
}
object BarParser extends JsonParser {
def deserialize(jsonBar: String): Bar = {
Serialization.read[Bar](jsonBar)
}
def serialize(bar: Bar): String = {
Serialization.write[Bar](bar)
}
}
object FooParser extends JsonParser {
def deserialize(jsonFoo: String): Foo = {
Serialization.read[Foo](jsonFoo)
}
def serialize(foo: Foo): String = {
Serialization.write[Foo](foo)
}
}
object Main {
def main(args: Array[String]): Unit = {
val foo = Foo("foo")
println(FooParser.serialize(foo)) // {"foo":"foo"}
println(FooParser.deserialize(FooParser.serialize(foo))) // Foo(foo)
}
}
Above it is clear that the logic to serialize and deserialize is repeated. This is one of the things I've tried (which doesn't compile).
Attempt to Solve
case class Bar(product: String, date: ZonedDateTime)
case class Foo(title: String)
abstract class GenericJsonParser[T] {
private case object ZDTSerializer extends CustomSerializer[ZonedDateTime](_ => (
{ case JString(s) => ZonedDateTime.parse(s) },
{ case zdt: ZonedDateTime => JString(zdt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"))) }
))
implicit val formats = DefaultFormats + ZDTSerializer
def deserialize(json: String): T = {
Serialization.read[T](json) // No Manifest available for T
}
def serialize(thing: T): String = {
Serialization.write[T](thing) // type arguments [A] conform to the bounds of none of the overloaded alternatives ...
}
}
object BarJsonParser extends GenericJsonParser[Bar]
object FooParser extends GenericJsonParser[Foo]
Any guidance would be appreciated.
I think you can use Json.format[ACaseClass], for example:
import play.api.libs.json.{Format, Json}
case class ACaseClass(value: String, anotherValue: Int)
implicit val formatACaseClass = Json.format[ACaseClass]
I guess for Seralization.read and write you still have to pass an implicit value, jvm need must know how to read/write your object/
Related
when I try to wrap my query in BSONDocument and putting my enumeratum enum as the value it docent compile.
for example, my enum:
sealed trait ProcessingStatus extends EnumEntry with UpperSnakecase
object ProcessingStatus extends Enum[ProcessingStatus] with ReactiveMongoBsonEnum[ProcessingStatus] {
val values: IndexedSeq[ProcessingStatus] = findValues
case object Processing extends ProcessingStatus
case object Done extends ProcessingStatus
}
and I have play json serializer that explains how to serialize:
object JsonSerialization {
import reactivemongo.api.bson._
implicit object ProcessingStatusReader extends BSONReader[ProcessingStatus] {
override def readTry(bson: BSONValue): Try[ProcessingStatus] = bson match {
case BSONString(s) => bson.asTry[ProcessingStatus]
case _ => Failure(new RuntimeException("String value expected"))
}
}
implicit object ProcessingStatusWriter extends BSONWriter[ProcessingStatus] {
override def writeTry(t: ProcessingStatus): Try[BSONString] = Try(BSONString(t.entryName))
}
//Report Serializers
implicit val ProcessingStatusFormat: Format[ProcessingStatus] = EnumFormats.formats(ProcessingStatus)
implicit val ReportFormat: OFormat[Report] = Json.format[Report]
}
and now in my dao this does not compile:
import reactivemongo.play.json.compat.json2bson.{toDocumentReader, toDocumentWriter}
import serializers.JsonSerialization._
def findReport(reportId: String) = {
val test = BSONDocument("123" -> ProcessingStatus.Processing) // dosent compile
}
screenshot:
compilation error:
overloaded method apply with alternatives:
(elms: Iterable[(String, reactivemongo.api.bson.BSONValue)])reactivemongo.api.bson.BSONDocument <and>
(elms: reactivemongo.api.bson.ElementProducer*)reactivemongo.api.bson.BSONDocument
cannot be applied to ((String, enums.ProcessingStatus.Done.type))
val test = BSONDocument("status" -> ProcessingStatus.Done)
An IDE error is not a compilation error (recommend to use sbt and its console to tests).
Your code (simplified as bellow), is compiling fine, whatever is telling the IDE (which is wrong).
import reactivemongo.api.bson._
import scala.util.Try
trait ProcessingStatus {
def entryName = "foo"
}
object JsonSerialization {
implicit object ProcessingStatusWriter extends BSONWriter[ProcessingStatus] {
override def writeTry(t: ProcessingStatus): Try[BSONString] = Try(BSONString(t.entryName))
}
}
import JsonSerialization._
BSON.write(new ProcessingStatus {})
Note.1: writeTry doesn't override anything, so the modifier is useless (and can lead to missunderstanding).
Note.2: Try(..) with a pure value such as BSONString(t.entryName) is over-engineered, rather use Success(..).
Note.3: Convenient factories are available such as val w = BSONWriter[T] { t => ... }.
Edit:
The typeclass BSONWriter (as most typeclass) is invariant, so having a BSONWriter[T] in the implicit scope doesn't allow to resolve a BSONWriter[U] forSome { U <: T }.
trait ProcessingStatus {
def entryName: String
}
object ProcessingStatus {
case object Done extends ProcessingStatus { val entryName = "done" }
}
object JsonSerialization {
implicit object ProcessingStatusWriter extends BSONWriter[ProcessingStatus] {
override def writeTry(t: ProcessingStatus): Try[BSONString] = Try(BSONString(t.entryName))
}
}
import JsonSerialization._
BSON.write(ProcessingStatus.Done
/*
<console>:32: error: could not find implicit value for parameter writer: reactivemongo.api.bson.BSONWriter[ProcessingStatus.Done.type]
BSON.write(ProcessingStatus.Done)
*/
// --- BUT ---
BSON.write(ProcessingStatus.Done: ProcessingStatus)
// Success(BSONString(done))
Also exposing Done (and other cases) as ProcessingStatus in the API is working.
import reactivemongo.api.bson._
import scala.util.Try
sealed trait ProcessingStatus {
def entryName: String
}
object ProcessingStatus {
val Done: ProcessingStatus = new ProcessingStatus { val entryName = "done" }
}
object JsonSerialization {
implicit object ProcessingStatusWriter extends BSONWriter[ProcessingStatus] {
override def writeTry(t: ProcessingStatus): Try[BSONString] = Try(BSONString(t.entryName))
}
}
import JsonSerialization._
BSON.write(ProcessingStatus.Done)
I want to use case class to replace primitive type in json4s, since I want to put business method in these case class.
for example:
import org.json4s.{CustomSerializer, DefaultFormats, NoTypeHints}
import org.json4s.JsonAST.{JField, JInt, JObject}
import org.json4s.jackson.Serialization
import org.json4s.jackson.Serialization.read
object Json4sExercise {
trait ObjectHolder[T] {
}
case class IntValue(v: Int) extends ObjectHolder[Int]
case class StringValue(v: String) extends ObjectHolder[String]
class IntSerializer extends CustomSerializer[IntValue](format => ( {
case JInt(x) => IntValue(x.toInt)
}, {
case IntValue(x) => JInt(BigInt(x))
}
))
implicit val formats = Serialization.formats(NoTypeHints) + new IntSerializer
def main(args: Array[String]): Unit = {
println(read[Map[String, _]](""" {"price":350} """))
}
}
But instead of what I wanted: Map(price -> IntValue(x:350)), it printed Map(price -> 350)
You need to be specific with the type you want to read
println(read[Map[String, IntValue]](""" {"price":350} """))
Map(price -> IntValue(350))
I have class as below
trait RiskCheckStatusCode {
def code: String
def isSuccess: Boolean
}
object RiskCheckStatusCode {
val SUCCESS = SuccessRiskCheckStatusCode("1.1.1")
val FAIL = FailRiskCheckStatusCode("2.2.2")
case class SuccessRiskCheckStatusCode(code: String) extends RiskCheckStatusCode {
override def isSuccess = true
}
object SuccessRiskCheckStatusCode {
import spray.json.DefaultJsonProtocol._
implicit val formatter = jsonFormat1(SuccessRiskCheckStatusCode.apply)
}
case class FailRiskCheckStatusCode(code: String) extends RiskCheckStatusCode {
override def isSuccess = false
}
object FailRiskCheckStatusCode {
import spray.json.DefaultJsonProtocol._
implicit val formatter = jsonFormat1(FailRiskCheckStatusCode.apply)
}
}
and now I would like to convert the list of RiskCheckStatusCode to json
object Main extends App{
import spray.json._
import spray.json.DefaultJsonProtocol._
val l = List(RiskCheckStatusCode.SUCCESS, RiskCheckStatusCode.FAIL)
implicit object RiskCheckStatusCodeJsonFormat extends JsonWriter[RiskCheckStatusCode] {
override def write(obj: RiskCheckStatusCode): JsValue = obj match {
case obj: SuccessRiskCheckStatusCode => obj.toJson
case obj: FailRiskCheckStatusCode => obj.toJson
}
}
def json[T](list: T)(implicit formatter: JsonWriter[T]) = {
print(list.toJson)
}
json(l)
}
but the json method can not find jsonWriter[RiskCheckStatusCode].
Can you explain why? Maybe should I do it differently for trait type?
Edit:
It works for
val l: RiskCheckStatusCode = RiskCheckStatusCode.SUCCESS
so the problem is with List[RiskCheckStatusCode] because I have a formatter for RiskCheckStatusCode, not for List[RiskCheckStatusCode]. I tried import DefaultJsonProtocol but it still does not work.
import spray.json.DefaultJsonProtocol._
I have to change the definitions? From
implicit object RiskCheckStatusCodeJsonFormat extends JsonWriter[RiskCheckStatusCode]
to
implicit object RiskCheckStatusCodeJsonFormat extends JsonWriter[List[RiskCheckStatusCode]]
error:
Error:(28, 7) Cannot find JsonWriter or JsonFormat type class for List[com.example.status.RiskCheckStatusCode]
json(l)
Error:(28, 7) not enough arguments for method json: (implicit formatter: spray.json.JsonWriter[List[com.example.status.RiskCheckStatusCode]])Unit.
Unspecified value parameter formatter.
json(l)
Your code is fine you are just not having toJson in your scope (it is located in the package object of spray.json).
Add it and your code should compile:
object Main extends App with DefaultJsonProtocol {
import spray.json._
// ...
}
Furthermore spray has some issues to lift JsonWriter through derived formats (see this for details).
You can switch to JsonFormat instead:
implicit object RiskCheckStatusCodeJsonFormat extends JsonFormat[RiskCheckStatusCode] {
override def write(obj: RiskCheckStatusCode): JsValue = obj match {
case obj: SuccessRiskCheckStatusCode => obj.toJson
case obj: FailRiskCheckStatusCode => obj.toJson
}
override def read(json: JsValue): RiskCheckStatusCode = ???
}
In addition, to cleanup the type of your List change the definition of RiskCheckStatusCode to (this explains more details):
sealed trait RiskCheckStatusCode extends Serializable with Product
I have a requirement to intercept toString of DateTime, LocalDate and Option in the runTime.
#implicitNotFound("No member of type class ReportString in scope for ${T}")
trait ReportStringTransformer[T] {
def toReportString(e: T): String
}
object ReportStringTransformer {
implicit object ReportStringTransformerDateTime
extends ReportStringTransformer[DateTime] {
override def toReportString(e: DateTime): String =
ISODateTimeFormat.dateTime().print(e)
}
implicit object ReportStringTransformerDate
extends ReportStringTransformer[LocalDate] {
override def toReportString(e: LocalDate): String =
ISODateTimeFormat.date().print(e)
}
implicit def ReportStringTransformerOpt[T]: ReportStringTransformer[Option[T]] =
new ReportStringTransformer[Option[T]] {
override def toReportString(e: Option[T]): String = e match {
case Some(obj) => ReportStringTransform.transform(obj)
case None => ""
}
}
}
object ReportStringTransform {
def transform[T](obj: T)(implicit t: ReportStringTransformer[T]): String =
t.toReportString(obj)
}
I could add a default transformer for Any class at the end which could only
be picked up after these, but is there any other cleaner way to do?
Your implementation could be simplified as follows:
#implicitNotFound("No member of type class Show in scope for ${T}")
case class Show[T](f: T => String) extends AnyVal
object Show {
implicit val showDateTime = Show[DateTime](ISODateTimeFormat.dateTime() print _)
implicit val showDate = Show[LocalDate](ISODateTimeFormat.date() print _)
implicit def showOpt[T](implicit s: Show[T]) = Show[Option[T]](_.fold("")(s.f))
}
To have a fallback for anything that is not a DateTime, a LocalDate or an Option of either the these, you could the following trait as a paraent of object Show
trait LowPriorityShow {
implicit def showAnything[T] = Show[T](_.toString)
}
object Show extends LowPriorityShow { ... }
Probably late to the party, but your issue on your gist here, is that the implicit resolution picks up the type Some[String] for the second case you put. other than that, the solution given by OlivierBlanvillain is great and it should solve your needs.
You can verify that this is your case by casting to Option[String]
println(MyShow.show(Some("abc"): Option[String])) // this will work as you expected
playing with variance/covariance you can make it work as expected link.
import org.joda.time.{DateTime, DateTimeZone}
import org.joda.time.format.ISODateTimeFormat
trait Show[-T] {
def show: T => String
}
trait LowPriorityShow {
implicit def showAnything[T] = new Show[T] {
def show: T => String = _.toString
}
}
object Show extends LowPriorityShow {
implicit val showString: Show[String] = new Show[String] {
def show: String => String = identity
}
implicit val showDateTime: Show[DateTime] =
new Show[DateTime] {
def show: DateTime => String =
ISODateTimeFormat.dateTime().withZone(DateTimeZone.forID("America/New_York")).print
}
implicit def showOpt[T : Show]: Show[Option[T]] =
new Show[Option[T]] {
def show: Option[T] => String = _.fold("")(implicitly[Show[T]].show)
}
def show[T : Show](v: T): String = implicitly[Show[T]].show(v)
}
println(Show.show(DateTime.now()))
println(Show.show(Some("abc")))
println(Show.show(12))
I am facing some weird problem with typeclass below: for some reason implicit object ContentUploader is not resolved on call to upload method of DemoActor.
import akka.actor.Actor
import java.io.File
import org.slf4j.LoggerFactory
class DemoActor extends Actor {
import DemoActor.UploaderImpl._
override def receive = {
case (x: DemoActor.Content) =>
DemoActor.upload(x)
}
}
object DemoActor {
val LOG = LoggerFactory.getLogger("DemoActor")
sealed trait UploadData {
val data: Array[File]
}
case class Content(data: Array[File]) extends UploadData
case class UploadResult(url: String, contentType: String, size: Long)
trait S3Uploader[T <: UploadData] {
def uploadToS3(filez: Array[File]): Iterable[UploadResult]
}
object UploaderImpl {
val LOG = LoggerFactory.getLogger("Uploader")
private def contentType(name: String): String = {
"application/octet-stream"
}
private def doUpload(filez: Array[File], bucketName: String) = {
LOG.debug("Uploading: {} to {}", filez, bucketName)
filez.flatMap {
case f =>
try {
val key = f.getName
val mime = contentType(f.getName)
Some(UploadResult("http://" + bucketName + ".s3.amazonaws.com/" + key, mime, f.length()))
} catch {
case e =>
LOG.error("Can not upload", e)
None
}
}
}
implicit object ContentUploader extends S3Uploader[Content] {
lazy val bucketName = "resources.aws.bucketname"
lazy val awsSecret = "resources.aws.secret.key"
lazy val awsAccess = "resources.aws.access.key"
override def uploadToS3(filez: Array[File]) = doUpload(filez, bucketName)
}
}
def upload[T <: UploadData](src: T)(implicit uploader: S3Uploader[T]) = uploader.uploadToS3(src.data)
}
What have I missed here?
UPD
if I move definition of class for DemoActor inside object DemoActor, like
import akka.actor.Actor
import java.io.File
import org.slf4j.LoggerFactory
object DemoActor {
val LOG = LoggerFactory.getLogger("DemoActor")
sealed trait UploadData {
val data: Array[File]
}
case class Content(data: Array[File]) extends UploadData
case class UploadResult(url: String, contentType: String, size: Long)
trait S3Uploader[UploadData] {
def uploadToS3(filez: Array[File]): Iterable[UploadResult]
}
object UploaderImpl {
val LOG = LoggerFactory.getLogger("Uploader")
private def contentType(name: String): String = {
"application/octet-stream"
}
private def doUpload(filez: Array[File], bucketName: String) = {
LOG.debug("Uploading: {} to {}", filez, bucketName)
filez.flatMap {
case f =>
try {
val key = f.getName
val mime = contentType(f.getName)
Some(UploadResult("http://" + bucketName + ".s3.amazonaws.com/" + key, mime, f.length()))
} catch {
case e =>
LOG.error("Can not upload", e)
None
}
}
}
implicit object ContentUploader extends S3Uploader[DemoActor.Content] {
lazy val bucketName = "resources.aws.bucketname"
lazy val awsSecret = "resources.aws.secret.key"
lazy val awsAccess = "resources.aws.access.key"
override def uploadToS3(filez: Array[File]) = doUpload(filez, bucketName)
}
}
def upload[T <: UploadData](src: T)(implicit uploader: S3Uploader[T]) = uploader.uploadToS3(src.data)
class DemoActor extends Actor {
import DemoActor.UploaderImpl._
override def receive = {
case (x: DemoActor.Content) =>
DemoActor.upload(x)
}
}
}
then everything works well. Are there some issues with namespacing?
It is not finding it because implicit forward references must be explicitly typed to be considered, and this one isn't.
If this is confusing, maybe two ways of fixing it might make it clear. First, you can declare the type of the implicit. Remove the implicit from the object, and declare a val pointing to it:
implicit val contentUploader: S3Uploader[DemoActor.Content] = ContentUploader
The second way is moving the class DemoActor declaration to the end of the file, so it stays after the the object DemoActor declaration.
The reason it works like this is that the compiler must search for the implicit before the rest of the file is fully typed, so it doesn't know, at that time, that object ContentUploader satisfy the search.