I have a set of case objects that inherits from a trait as below:
sealed trait UserRole
case object SuperAdmin extends UserRole
case object Admin extends UserRole
case object User extends UserRole
I want to serialize this as JSON and I just used the Format mechanism:
implicit val userRoleFormat: Format[UserRole] = Json.format[UserRole]
But unfortunately, the compiler is not happy and it says:
No unapply or unapplySeq function found
What is wrong in my case objects?
Ok I figured out what has to be done!
Here it is:
implicit object UserRoleWrites extends Writes[UserRole] {
def writes(role: UserRole) = role match {
case Admin => Json.toJson("Admin")
case SuperAdmin => Json.toJson("SuperAdmin")
case User => Json.toJson("User")
}
}
Another option is to override def toString like this:
File: Status.scala
package models
trait Status
case object Active extends Status {
override def toString: String = this.productPrefix
}
case object InActive extends Status {
override def toString: String = this.productPrefix
}
this.productPrefix will give you case object name
File: Answer.scala
package models
import play.api.libs.json._
case class Answer(
id: Int,
label: String,
status: Status
) {
implicit val answerWrites = new Writes[Answer] {
def writes(answer: Answer): JsObject = Json.obj(
"id" -> answer.id,
"label" -> answer.label,
"status" -> answer.status.toString
)
}
def toJson = {
Json.toJson(this)
}
}
File: Controller.scala
import models._
val jsonAnswer = Answer(1, "Blue", Active).toJson
println(jsonAnswer)
you get:
{"id":1,"label":"Blue","status":"Active"}
Hope this helps!
Related
I am a beginner in scala, I have a case class for the response I am getting as json payload
case class CallbackPayload(
automation_id: String,
business_key: String,
state: String #received , validating
)
I have a sealed abstract class
object ExecutionStatus {
sealed abstract class ExecutionState(status: String) {
override def toString: String = status
}
case object RECEIVED extends ExecutionState("received")
case object VALIDATING extends ExecutionState("validating")
}
Now based on the response payload state I want to do a match against the ExecutionStatus objects
Something like below
def callback(payload: CallbackPayload): Either[Throwable, Json] = {
payload.state match {
case VALIDATING => Right(Json.obj("status" -> Json.fromString("validating")))
.....
}
}
Now because of payload.state type string I am unable to . How to do that in scala.
It looks like you may want to match on the status string, like so (using a simplified model of your code):
object ExecutionStatus {
sealed abstract class ExecutionState(val status: String) {
override def toString: String = status
}
case object RECEIVED extends ExecutionState("received")
case object VALIDATING extends ExecutionState("validating")
}
def parse(state: String): Either[Throwable, Map[String, String]] = {
state match {
case ExecutionStatus.RECEIVED.status =>
Right(Map("status" -> "received"))
case ExecutionStatus.VALIDATING.status =>
Right(Map("status" -> "validating"))
case _ =>
Left(new IllegalArgumentException(s"unrecognized state $state"))
}
}
assert(parse("received") == Right(Map("status" -> "received")))
assert(parse("validating") == Right(Map("status" -> "validating")))
assert(parse("foo").isLeft)
You can play around with this code here on Scastie.
Notice that in order to do so I had to make status a val so that it's public (without it, status is only a constructor parameter).
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/
In Scala with Play and Slick, I've implemented enum Country as a case class, and would like to read/write it to JSON as a string. This is the complete code:
package models.enums
import play.api.libs.json._
import slick.lifted.MappedTo
case class Country(id: String) extends MappedTo[String] {
override def value: String = id
}
object Country {
val France = Country("FR")
val Germany = Country("DE")
implicit val writes = Writes[Country](
(site: Country) => JsString(site.value)
)
// this line I need help with
implicit val reads = Json.reads[Country]
}
The Country enum is used by other models, e.g.
{
"name": "Robo"
"country": "DE"
}
The code above writes the enum as a plain String which is what I want, but I can't figure out how to implement the implicit read part. Currently the read would only work if the JSON is like:
{
"name": "Robo"
"country": {
"id": "DE"
}
}
I'm sure the answer is simple, just can't figure out the correct syntax.
import play.api.libs.json._
import play.api.libs.json.Reads._
implicit val reads = new Reads[Country] {
override def reads(json: JsValue): JsResult[Country] = for {
id <- (json \ "country").validate[String]
} yield Country(id)
}
(json \ "country").validate[String] returns a JsResult[String] which is then mapped (via yield) to a JsResult[Country]
or
import play.api.libs.json._
import play.api.libs.json.Reads._
implicit val reads = new Reads[Country] {
override def reads(json: JsValue): JsResult[Country] =
(json \ "country").validate[String].map(Country(_))
or
import play.api.libs.json._
import play.api.libs.json.Reads._
import play.api.libs.functional.syntax._
implicit val reads: Reads[Country] = (__ \ "country").reads[String].map(Country(_))
EDIT:
Regarding your comment on validation, I'd just add that to your case class, not the json parser:
case class Country(id: String) extends MappedTo[String] {
private val validIds = Set(France, Germany).map(_.id)
require(validIds contains id, s"id must be one of $validIds")
override def value: String = id
}
the trick is that statements you put in the body of your class will be part of the constructor. The require call there will throw an exception if anyone tries to create a Country with an ID which is not in your list of allowed IDs.
I recommend using sealed class. If the selector of a pattern match is an instance of a sealed class, the compilation of pattern matching can emit warnings which diagnose that a given set of patterns is not exhaustive, i.e. that there is a possibility of a MatchError being raised at run-time.
Example from my project:
import com.pellucid.sealerate
import play.api.libs.json._
sealed abstract class TokenKind(val value: String)
object TokenKind {
case object PasswordReset extends TokenKind("passwordReset")
case object AccountConfirmation extends TokenKind("accountConfirmation")
def values: Set[TokenKind] = sealerate.values[TokenKind]
def apply(value: String): TokenKind = values.find(_.value == value).getOrElse(throw new RuntimeException(s"Can't construct TokenKind from: $value"))
implicit def tokenKindWrites = new Writes[TokenKind] {
override def writes(o: TokenKind): JsValue = JsString(o.value)
}
implicit def tokenKindReads = new Reads[TokenKind] {
override def reads(json: JsValue): JsResult[TokenKind] = json match {
case JsString(string) => JsSuccess(TokenKind(string))
case _ => JsError("validate.error.invalidTokenKind")
}
}
}
I had a similar problem that solved this way:
object VoteType extends Enumeration {
val Up, Down = Value
implicit val voteTypeMappeer = MappedColumnType.base[VoteType.Value, String](_.toString, VoteType.withName)
}
I am working with the Play Framework and ReactiveMongo. I am trying to write a reader and a writer for my class called Platforms. I am trying to use a type that I created as scala enum, but I don't know how the reader/writer syntax should be defined. Can someone help me figure out the correct syntax?
import reactivemongo.bson._
sealed trait PlatformType { def name: String }
case object PROPER extends PlatformType { val name = "PROPER" }
case object TRANSACT extends PlatformType { val name = "TRANSACT" }
case object UPPER extends PlatformType { val name = "UPPPER" }
case class Platforms(
id: Option[BSONObjectID],
Platform: PlatformType,
Active: Boolean,
SystemIds:List[String],
creationDate: Option[DateTime],
updateDate: Option[DateTime])
object Platforms {
implicit object PlatformsBSONReader extends BSONDocumentReader[Platforms] {
def read(doc: BSONDocument): Platforms =
Platforms(
doc.getAs[BSONObjectID]("_id"),
doc.getAs[PlatformType]("Platform").get,
doc.getAs[Boolean]("Active").get,
doc.getAs[List[String]]("SystemIds").get,
doc.getAs[BSONDateTime]("creationDate").map(dt => new DateTime(dt.value)),
doc.getAs[BSONDateTime]("updateDate").map(dt => new DateTime(dt.value)))
}
implicit object PlatformsBSONWriter extends BSONDocumentWriter[Platforms] {
def write(platforms: Platforms): BSONDocument =
BSONDocument(
"_id" -> platforms.id.getOrElse(BSONObjectID.generate),
"Platform" -> platforms.Platform,
"Active" -> platforms.Active,
"SystemIds" -> platforms.SystemIds,
"creationDate" -> platforms.creationDate.map(date => BSONDateTime(date.getMillis)),
"updateDate" -> platforms.updateDate.map(date => BSONDateTime(date.getMillis)))
}
}
For PlatformType
implicit object PTW extends BSONWriter[PlatformType, BSONString] {
def write(t: PlatformType): BSONString = BSONString(n.type)
}
implicit object PTR extends BSONReader[BSONValue, PlatformType] {
def read(bson: BSONValue): PlatformType = bson match {
case BSONString("PROPER") => PROPER
// ...
}
}
There is an online documentation about the BSON readers & writers in ReactiveMongo.
Using Spray with spray-json for a system, version:
"io.spray" %% "spray-json" % "1.2.6"
I cannot figure how to get custom JsonFormat definitions to work for serialization that is being handled by spray-routing.
I've had two separate circumstances that have failed.
1. Nested Case Classes
Basic case class JSON serialization has worked fine
case class Something(a: String, b: String)
implicit val something2Json = jsonFormat3(Something)
However if I have a nested case class in the case class to be serialized, I can resolve compile issues by providing another JsonFormat implicit, yet at run-time it refuses to serialize
case class Subrecord(value: String)
case class Record(a: String, b: String, subrecord: Subrecord)
object MyJsonProtocol extends DefaultJsonProtocol {
implicit object SubrecordJsonFormat extends JsonFormat[Subrecord] {
def write(sub: Subrecord) = JsString(sub.value)
def read(value: JsValue) = value match {
case JsString(s) => Subrecord(s)
case _ => throw new DeserializationException("Cannot parse Subrecord")
}
}
implicit val record2Json = jsonFormat3(Record)
}
This will throw a MappingException at runtime, explaining there is no usable value for subrecord
2. Trait with various 0-N case extensions
Here I have a trait that serves as a capturing type for a group of case classes. Some of the extending classes have vals while others have no vals and are objects. When serialization occurs, it seems like my implicit defined JsonFormat is completely ignored and I'm just give an empty JsObject, particularly when the actual underlying type was one of the case object's with no vals.
sealed trait Errors
sealed trait ErrorsWithReason extends Errors {
def reason: String
}
case class ValidationError(reason: String) extends ErrorsWithReason
case object EntityNotFound extends Errors
case class DatabaseError(reason: String) extends ErrorsWithReason
object MyJsonProtocol extends DefaultJsonProtocol {
implicit object ErrorsJsonFormat extends JsonFormat[Errors] {
def write(err: Errors) = failure match {
case e: ErrorsWithReason => JsString(e.reason)
case x => JsString(x.toString())
}
def read(value: JsValue) = {
value match {
//Really only intended to serialize to JSON for API responses
case _ => throw new DeserializationException("Can't reliably deserialize Error")
}
}
}
}
So given the above, if the actual type being serialized is EntityNotFound, then the serialization becomes a RootJsonFormat turning into {}. If it's an ErrorsWithReason then it becomes a RootJsonFormat turning into { "reason": "somevalue" }. I may be confused with how the JsonFormat definition is supposed to work, but it doesn't seem to be using my write method at all and instead has suddenly figured out how to serialize on its own.
EDIT
Specific serialization cases are using read/deserialization like:
entity(as[JObject]) { json =>
val extraction: A = json.extract[A]
}
And write/serialization with the complete directive.
I now am realizing thanks to the first answer posted here that my JsonDefaultProtocol and JsonFormat implementations are for spray-json classes, meanwhile the entity directive extraction in the deserialization is using json4s JObject as opposed to spray-json JsObject.
Another approach for clean JSON output
import spray.json._
import spray.json.DefaultJsonProtocol._
// #1. Subrecords
case class Subrecord(value: String)
case class Record(a: String, b: String, subrecord: Subrecord)
implicit object RecordFormat extends JsonFormat[Record] {
def write(obj: Record): JsValue = {
JsObject(
("a", JsString(obj.a)),
("b", JsString(obj.b)),
("reason", JsString(obj.subrecord.value))
)
}
def read(json: JsValue): Record = json match {
case JsObject(fields)
if fields.isDefinedAt("a") & fields.isDefinedAt("b") & fields.isDefinedAt("reason") =>
Record(fields("a").convertTo[String],
fields("b").convertTo[String],
Subrecord(fields("reason").convertTo[String])
)
case _ => deserializationError("Not a Record")
}
}
val record = Record("first", "other", Subrecord("some error message"))
val recordToJson = record.toJson
val recordFromJson = recordToJson.convertTo[Record]
println(recordToJson)
assert(recordFromJson == record)
If you need both reads and writes you can do it this way:
import spray.json._
import spray.json.DefaultJsonProtocol._
// #1. Subrecords
case class Subrecord(value: String)
case class Record(a: String, b: String, subrecord: Subrecord)
implicit val subrecordFormat = jsonFormat1(Subrecord)
implicit val recordFormat = jsonFormat3(Record)
val record = Record("a", "b", Subrecord("c"))
val recordToJson = record.toJson
val recordFromJson = recordToJson.convertTo[Record]
assert(recordFromJson == record)
// #2. Sealed traits
sealed trait Errors
sealed trait ErrorsWithReason extends Errors {
def reason: String
}
case class ValidationError(reason: String) extends ErrorsWithReason
case object EntityNotFound extends Errors
case class DatabaseError(reason: String) extends ErrorsWithReason
implicit object ErrorsJsonFormat extends JsonFormat[Errors] {
def write(err: Errors) = err match {
case ValidationError(reason) =>
JsObject(
("error", JsString("ValidationError")),
("reason", JsString(reason))
)
case DatabaseError(reason) =>
JsObject(
("error", JsString("DatabaseError")),
("reason", JsString(reason))
)
case EntityNotFound => JsString("EntityNotFound")
}
def read(value: JsValue) = value match {
case JsString("EntityNotFound") => EntityNotFound
case JsObject(fields) if fields("error") == JsString("ValidationError") =>
ValidationError(fields("reason").convertTo[String])
case JsObject(fields) if fields("error") == JsString("DatabaseError") =>
DatabaseError(fields("reason").convertTo[String])
}
}
val validationError: Errors = ValidationError("error")
val databaseError: Errors = DatabaseError("error")
val entityNotFound: Errors = EntityNotFound
assert(validationError.toJson.convertTo[Errors] == validationError)
assert(databaseError.toJson.convertTo[Errors] == databaseError)
assert(entityNotFound.toJson.convertTo[Errors] == entityNotFound)