Play JSON Reads Format for Case Objects with Sealed Traits - json

I have a sealed trait that encapsulates an enum type like below:
sealed trait MyType
object MyType {
case object Type1 extends MyType
case object Type2 extends MyType
}
I will get this as a JSON request and I'm finding it a bit difficult to read the incoming String to a case object. Here is what I have:
implicit val myFormat: Format[MyType] = new Format[MyType] {
override def reads(json: JsValue): JsResult[MyType] = (json \ "type").as[String] match {
case "MyType1" => MyType.Type1
case "MyType2" => MyType.Type2
}
.....
.....
It however fails compilation saying that:
Expected JsResult[MyType] but found MyType.Type1.type
What is wrong with my approach?

Fixed it like this:
implicit val myTypeReads: Reads[MyType] = Reads {
case JsString("MyType1") => JsSuccess(MyType1)
case JsString("MyType2") => JsSuccess(MyType2)
case jsValue => JsError(s"MyType $jsValue is not one of supported [${MyType.values.mkString(",")}]")
}

Related

Circe Encoding Sealed Traits and Co Product Case class Fails

I have the following case class:
case class SmartMeterData(
dateInterval: SmartMeterDataInterval = HalfHourInterval(),
powerUnit: PowerUnit = KWH,
smartMeterId: String,
timestamp: String,
value: Double
) {
def timeIntervalInDuration: FiniteDuration = dateInterval match {
case HalfHourInterval(_) => FiniteDuration(30, TimeUnit.MINUTES)
case FullHourInterval(_) => FiniteDuration(60, TimeUnit.MINUTES)
}
}
object SmartMeterData {
sealed trait SmartMeterDataInterval { def interval: String }
case class HalfHourInterval(interval: String = "HH") extends SmartMeterDataInterval
case class FullHourInterval(interval: String = "FH") extends SmartMeterDataInterval
sealed trait PowerUnit
case object WH extends PowerUnit
case object KWH extends PowerUnit
case object MWH extends PowerUnit
}
I just wrote a very simple unit test to see if Circe works for my scenario:
"SmartMeterData" should "successfully parse from a valid JSON AST" in {
val js: String = """
{
"dateInterval" : "HH",
"powerUnit" : "KWH",
"smartMeterId" : "LCID-001-X-54",
"timestamp" : "2012-10-12 00:30:00.0000000",
"value" : 23.0
}
"""
val expectedSmartMeterData = SmartMeterData(smartMeterId = "LCID-001-X-54", timestamp = "2012-10-12 00:30:00.0000000", value = 23.0)
decode[SmartMeterData](js) match {
case Left(err) => fail(s"Error when parsing valid JSON ${err.toString}")
case Right(actualSmartMeterData) =>
assert(actualSmartMeterData equals expectedSmartMeterData)
}
}
But it fails with the following error:
Error when parsing valid JSON DecodingFailure(CNil, List(DownField(dateInterval)))
Is there a known limitation with circe where it does not yet work for my case above?
There's no reason that Circe wouldn't work, but the way you've designed the data model, there's basically zero chance of any Scala JSON library that can automatically generate an encoder/decoder working without a lot of manual work.
For example
sealed trait SmartMeterDataInterval { def interval: String }
case class HalfHourInterval(interval: String = "HH") extends SmartMeterDataInterval
case class FullHourInterval(interval: String = "FH") extends SmartMeterDataInterval
the schema for both, in any Scala JSON library which automatically derives instances for case classes, would be along the lines of
{ "interval": "HH" }
for HalfHourInterval and
{ "interval": "FH" }
for FullHourInterval (i.e. because each has a single string field named interval, they're effectively the same class). In fact your model allows you to have FullHourInterval("HH"), which for at least one method of generating a decoder for an ADT hierarchy in circe (the one in the documentation which uses shapeless) would be the result of decoding { "interval": "HH" }, since that essentially takes the first constructor in lexical order which matches (i.e. FullHourInterval). If the intent is to only allow full- or half-hour intervals, then I'd suggest expressing that as:
case object HalfHourInterval extends SmartMeterDataInterval { def interval: String = "HH" }
case object FullHourInterval extends SmartMeterDataInterval { def interval: String = "FH" }
I'm not directly familiar with how circe encodes case objects, but you can pretty easily define an encoder and decoder for SmartMeterDataInterval:
object SmartMeterDataInterval {
implicit val encoder: Encoder[SmartMeterDataInterval] =
Encoder.encodeString.contramap[SmartMeterDataInterval](_.interval)
implicit val decoder: Decoder[SmartMeterDataInterval] =
Decoder.decodeString.emap {
case "HH" => Right(HalfHourInterval)
case "FH" => Right(FullHourInterval)
case _ => Left("not a valid SmartMeterDataInterval")
}
}
You would then do something similar to define an Encoder/Decoder for PowerUnit

Play json absent-sensitive Reads

I'd like to have a reads like Reads[Patch[T]] for a case class like this
sealed trait Patch[+T]
case class Update[+T](value: T) extends Patch[T]
case object Delete extends Patch[Nothing]
case object Ignore extends Patch[Nothing]
where a missing json value reads to Ignore, a null json value reads to Delete and a valid present value reads to Patch.
Is it possible to implement a Reads like this?
Json4s has a JNothing type, does play json have some way to achieve the same functionality (I know there is no nothing type under JsValue)?
Edit: for context on how this might be used see the json merge patch rfc.
Leaving aside discussions of whether Patch[Nothing] is a good idea, if we use this family of objects:
sealed trait Patch[+T]
case class Update[+T](value: T) extends Patch[T]
case object Delete extends Patch[Nothing]
case object Ignore extends Patch[Nothing]
We can get the desired behaviour by implementing a wrapper class:
case class PatchContainer[T](patch: Patch[T])
We have to do this as otherwise we lose the all-important distinction between a null value and a completely missing patch.
Now we can write a Reads for a PatchContainer[T] as long as we supply a suitable Reads[T] (e.g. for a String or Int etc):
class PatchContainerJson[T](implicit val rdst:Reads[T]) {
implicit val patchContainerReads = new Reads[PatchContainer[T]] {
override def reads(json: JsValue): JsResult[PatchContainer[T]] = {
json.validate[JsObject].map { obj =>
(obj \ "patch").asOpt[T].fold[PatchContainer[T]] {
if (obj.keys.contains("patch")) {
PatchContainer(Delete)
} else {
PatchContainer(Ignore)
}
} { v =>
PatchContainer(Update(v))
}
}
}
}
}
The "trick" here is detecting whether there is a patch key in the object (using keys.contains), to get the desired Delete vs Ignore behaviour.
Examples of usage:
scala> import play.api.libs.json._
scala> val json = Json.parse(""" { "patch": 42 } """ )
json: play.api.libs.json.JsValue = {"patch":42}
scala> val pcti = new PatchContainerJson[Int]()
scala> import pcti._
scala> val result = json.validate[PatchContainer[Int]]
result: play.api.libs.json.JsResult[models.PatchContainer[Int]] = JsSuccess(PatchContainer(Update(42)),)
scala> result.get.patch
res0: models.Patch[Int] = Update(42)
and
...
scala> val ignoredJson = Json.parse(""" { } """)
scala> ignoredJson.validate[PatchContainer[Int]]
res1: play.api.libs.json.JsResult[models.PatchContainer[Int]] = JsSuccess(PatchContainer(Ignore),)
and
scala> val deleteJson = Json.parse(""" { "patch": null } """)
scala> deleteJson.validate[PatchContainer[Int]]
res2: play.api.libs.json.JsResult[models.PatchContainer[Int]] = JsSuccess(PatchContainer(Delete),)

Json deserialization of Scala case objects with spray-json

I am trying to write a custom JsonReader using spray-json for the following domain model:
sealed trait OrderType
object OrderType {
case object MARKET extends OrderType
case object LIMIT extends OrderType
case object STOP extends OrderType
case object MARKET_IF_TOUCHED extends OrderType
case object TAKE_PROFIT extends OrderType
case object STOP_LOSS extends OrderType
case object TRAILING_STOP_LOSS extends OrderType
}
Here is the custom JsonReader I created for this purpose:
implicit object OrderTypeJsonReader extends JsonReader[OrderType] {
def read(value: JsValue): OrderType = value match {
case JsString("MARKET") => MARKET
case JsString("LIMIT") => LIMIT
case JsString("STOP") => STOP
case JsString("MARKET_IF_TOUCHED") => MARKET_IF_TOUCHED
case JsString("TAKE_PROFIT") => TAKE_PROFIT
case JsString("STOP_LOSS") => STOP_LOSS
case JsString("TRAILING_STOP_LOSS") => TRAILING_STOP_LOSS
case _ => deserializationError("OrderType expected")
}
}
Given that the json string and the name of the case object are the same, is there any way to avoid code duplication here?
You could try to replace pattern match with a partial function(or Map):
val orderTypes = List(MARKET, LIMIT, STOP, MARKET_IF_TOUCHED, TAKE_PROFIT, STOP_LOSS, TRAILING_STOP_LOSS)
val string2orderType: Map[JsValue, OrderType] =
orderTypes.map(ot => (JsString(ot.toString), ot)).toMap
implicit object OrderTypeJsonReader extends JsonReader[OrderType] {
def read(value: JsValue): OrderType =
string2orderType.getOrElse(value, deserializationError("OrderType expected"))
}
The disadvantage is that you have to specify the list of all case objects manually. You can try to use reflection to generate it.
Maybe this question would be helpful for that Getting subclasses of a sealed trait . Then you can have:
import scala.reflect.runtime.universe
private val tpe = universe.typeOf[OrderType]
private val clazz = tpe.typeSymbol.asClass
private def objectBy[T](name: String): T = Class.forName(OrderType.getClass.getName + name + "$").newInstance().asInstanceOf[T]
val string2orderType: Map[JsValue, OrderType] = clazz.knownDirectSubclasses.map { sc =>
val objectName = sc.toString.stripPrefix("object ")
(JsString(objectName), objectBy[OrderType](objectName))
}.toMap
implicit object OrderTypeJsonReader extends JsonReader[OrderType] {
def read(value: JsValue): OrderType = string2orderType.getOrElse(value, deserializationError("OrderType expected"))
}
Please, also see this discussion about adding a default case class format to Spray: https://github.com/spray/spray-json/issues/186
UPDATE to address the comments
Is it possible to 'generify' it for any type T? I have quite a few of those sealed trait / case object enumerations and would prefer to have the boilerplate kept to minimum.
I've come up with this:
import spray.json._
import Utils._
sealed trait OrderStatus
object OrderStatus {
case object Cancelled extends OrderStatus
case object Delivered extends OrderStatus
// More objects...
implicit object OrderStatusJsonReader extends ObjectJsonReader[OrderStatus]
}
sealed trait OrderType
object OrderType {
case object MARKET extends OrderType
case object LIMIT extends OrderType
// More objects...
implicit object OrderTypeJsonReader extends ObjectJsonReader[OrderType]
}
object Utils {
import scala.reflect.ClassTag
import scala.reflect.runtime.universe._
def objectBy[T: ClassTag](name: String): T = {
val c = implicitly[ClassTag[T]]
Class.forName(c + "$" + name + "$").newInstance().asInstanceOf[T]
}
def string2trait[T: TypeTag : ClassTag]: Map[JsValue, T] = {
val clazz = typeOf[T].typeSymbol.asClass
clazz.knownDirectSubclasses.map { sc =>
val objectName = sc.toString.stripPrefix("object ")
(JsString(objectName), objectBy[T](objectName))
}.toMap
}
class ObjectJsonReader[T: TypeTag : ClassTag] extends JsonReader[T] {
val string2T: Map[JsValue, T] = string2trait[T]
def defaultValue: T = deserializationError(s"${ implicitly[ClassTag[T]].runtimeClass.getCanonicalName } expected")
override def read(json: JsValue): T = string2T.getOrElse(json, defaultValue)
}
}
Then you can use it like:
import OrderType._
import OrderStatus._
JsString("MARKET").convertTo[OrderType]
JsString(OrderStatus.Cancelled.toString).convertTo[OrderStatus]
I also tried code from spray-json github issue and it can be used like so:
implicit val orderTypeJsonFormat: RootJsonFormat[OrderType] =
caseObjectJsonFormat(MARKET, LIMIT, STOP, MARKET_IF_TOUCHED, TAKE_PROFIT, STOP_LOSS, TRAILING_STOP_LOSS)
Unfortunately, this requires you to specify all of the objects explicitly. If you want it like so, then, I think, my first suggestion (without reflection) is better. (Because it is without reflection :-) )

json4s convert inherited class to json

I am trying to serialize / deserialize to json with json4s native the following case class :
case class UserId(value: String) extends MappedTo[String] with Guid
case class FootballTeamId(value: String) extends Guid
trait Guid extends Id[String] with MappedTo[String]
object Guid {
import scala.reflect.runtime._
import scala.reflect.runtime.universe._
def next[T <: Guid : TypeTag]: T = {
val tt = typeTag[T]
val constructor: MethodSymbol = tt.tpe.members.filter {
m ⇒
m.isMethod && m.asMethod.isConstructor
}.head.asMethod
currentMirror.reflectClass(tt.tpe.typeSymbol.asClass).reflectConstructor(constructor)(UUID.randomUUID().toString).asInstanceOf[T]
}
}
I can do like this :
case object UserIdSerializer extends CustomSerializer[UserId](format => ({
case JString(s) => {
UserId(s)
}
}, {
case s: UserId => JString(s.value)
}))
and have one custom serializer by type, but I'd rather be dry and have one custom serialiser that would do the job for all object of type Guid. I had no luck. I would be grateful for any help I could get on the topic !
Thanks

Vector deserialization by using lift-json

How can i deserialize json array using lift-json to scala vector?
For example:
case class Foo(bar: Vector[Bar])
trait Bar {
def value: Int
}
case class Bar1(value: Int) extends Bar
case class Bar2(value: Int) extends Bar
import net.liftweb.json.{ShortTypeHints, Serialization, DefaultFormats}
implicit val formats = new DefaultFormats {
override val typeHintFieldName = "type"
override val typeHints = ShortTypeHints(List(classOf[Foo],classOf[Bar1],classOf[Bar2]))
}
println(Serialization.writePretty(Foo(Vector(Bar1(1), Bar2(5), Bar1(1)))))
The result is:
{
"type":"Foo",
"bar":[{
"type":"Bar1",
"value":1
},{
"type":"Bar2",
"value":5
},{
"type":"Bar1",
"value":1
}]
}
Good. But when i try to deserialize this string
println(Serialization.read[Foo](Serialization.writePretty(Foo(Vector(Bar1(1), Bar2(5), Bar1(1))))))
i get an exception:
net.liftweb.json.MappingException: Parsed JSON values do not match
with class constructor args=List(Bar1(1), Bar2(5), Bar1(1)) arg
types=scala.collection.immutable.$colon$colon constructor=public
test.Foo(scala.collection.immutable.Vector)
It's means that json array associated with scala list, not vector type that defined in class Foo. I know that there is way to create custom serializer by extending net.liftweb.json.Serializer and include it to formats value. But how can i restore type of objects that stores in Vector. I wanna get result of deserializing like this:
Foo(Vector(Bar1(1), Bar2(5), Bar1(1)))
I've often been annoyed by the List-centricness of Lift, and have found myself needing to do similar things in the past. The following is the approach I've used, adapted a bit for your example:
trait Bar { def value: Int }
case class Bar1(value: Int) extends Bar
case class Bar2(value: Int) extends Bar
case class Foo(bar: Vector[Bar])
import net.liftweb.json._
implicit val formats = new DefaultFormats { outer =>
override val typeHintFieldName = "type"
override val typeHints =
ShortTypeHints(classOf[Bar1] :: classOf[Bar2] :: Nil) +
new ShortTypeHints(classOf[Foo] :: Nil) {
val FooName = this.hintFor(classOf[Foo])
override def deserialize = {
case (FooName, foo) => foo \ "bar" match {
case JArray(bars) => Foo(
bars.map(_.extract[Bar](outer, manifest[Bar]))(collection.breakOut)
)
case _ => throw new RuntimeException("Not really a Foo.")
}
}
}
}
Kind of ugly, and could probably be cleaned up a bit, but it works.
You could add an implicit conversion:
implicit def listToVect(list:List[Bar]):Vector[Bar] = list.map(identity)(breakOut)
after that, Serialization.read[Foo] works as expected.