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 :-) )
Related
In an Akka streams connection I receive JSON objects that looks like this:
{"op":"connection"...}
{"op":"status"...}
..etc
And I have a the following classes setup:
case class ResponseMessage(
op: Option[OpType],
)
case class ConnectionMessage(
a: Option[Int],
b: Option[Int],
) extends ResponseMessage
case class StatusMessage(
c: Option[Int],
d: Option[Int],
) extends ResponseMessage
type OpTypes = OpTypes.Value
object OpTypes extends Enumeration {
val Connection = Value("connection")
val Status = Value("status")
How can I write custom JsonFormat instances so that depending on the
value of op I create the correct type?
So that it can be used as such:
> jsValue.convertTo[ResponseMessage] And the outvalue will be either
> ConnectionMessage or StatusMessage?
Note: it's not best practice to extend case class i.e. ResponseMessage. Google "case class inheritance"...
About your question on JsonFormat, I'd define specific JsonFormats for each subclass, and then define the one for ResponseMessage base on those like:
import spray.json._
abstract class ResponseMessage(val op: Option[OpTypes])
object ResponseMessage {
implicit object jsonFormat extends JsonFormat[ResponseMessage] {
override def read(json: JsValue): ResponseMessage = json match {
case JsObject(fields) =>
fields.get("op") match {
case Some(JsString("connection")) => json.convertTo[ConnectionMessage]
case Some(JsString("status")) => json.convertTo[StatusMessage]
case op => // unknown op
}
case _ => // invalid json
}
override def write(obj: ResponseMessage): JsValue = obj match {
case connection: ConnectionMessage => connection.toJson
case status: StatusMessage => status.toJson
}
}
}
case class ConnectionMessage(
a: Option[Int],
b: Option[Int]
) extends ResponseMessage(Some(OpTypes.Connection))
object ConnectionMessage {
implicit object jsonFormat extends JsonFormat[ConnectionMessage] {
override def read(json: JsValue): ConnectionMessage =
// json -> ConnectionMessage
override def write(obj: ConnectionMessage): JsValue =
// ConnectionMessage -> json
}
}
case class StatusMessage(
c: Option[Int],
d: Option[Int]
) extends ResponseMessage(Some(OpTypes.Status))
object StatusMessage {
implicit object jsonFormat extends JsonFormat[StatusMessage] {
override def read(json: JsValue): StatusMessage =
// json -> StatusMessage
override def write(obj: StatusMessage): JsValue =
// StatusMessage -> json
}
}
type OpTypes = OpTypes.Value
object OpTypes extends Enumeration {
val Connection = Value("connection")
val Status = Value("status")
}
I have an enum class hierarchy like this:
abstract class EnumBase {
def code: String
}
abstract class EnumObject[EnumT <: EnumBase] {...}
I use this in many places. Here is one example:
sealed abstract case class StateCode(code: String) extends EnumBase
object StateCode extends EnumObject[StateCode] {...}
I would like to serialize a case class containing StateCode deeply nested inside into JSON. I have written the following custom serializer to do this:
class EnumSerializer[EnumT <: EnumBase: scala.reflect.ClassTag](enumObject: EnumObject[EnumT])
extends org.json4s.Serializer[EnumT] {
val EnumerationClass = scala.reflect.classTag[EnumT].runtimeClass
def deserialize(implicit format: Formats):
PartialFunction[(TypeInfo, JValue), EnumT] = {
case (TypeInfo(EnumerationClass, _), json) => json match {
...
}
}
def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
case i: EnumT => JString(i.code)
case _ => JString("BLAH")
}
}
This isn't working in that the resulting JSON is merely "stateCode":{} when I serialize a case class like this:
MyClass(id = 123,
version = 1,
year = 2016,
input = MyInput(
...
stateCode = StateCode.CO
)
)
Note I am using an Extraction.decompose on the MyInput instance within my custom deserializer for MyClass in the hope my custom serializer will kick in there.
I also tried writing a custom serializer dedicated solely to StateCode just to see if my attempt to be generic is the issue. That didn't help either.
Any ideas on how I can get JSON4S to serialize my enums?
You could try this:
abstract class EnumBase {
def code: String
}
abstract class EnumObject[EnumT <: EnumBase] {}
final case class StateCode(code: String) extends EnumBase
object StateCode extends EnumObject[StateCode] {
val CO = new StateCode("CO")
val CA = new StateCode("CA")
}
case class ClassWithStateCode(id: Int, x: StateCode)
case class MyClass(id: Int, version: Int, year: Int, co: StateCode)
Which doesn't require defining any serializer but this might not be what you are looking for.
Potential issue with what I assume you are doing:
object StateCode extends EnumObject[StateCode] {
val CO = new StateCode("CO") {}
val CA = new StateCode("CA") {}
}
is that you are creating anonymous class which is subtype of StateCode which is case class and you should be very careful extending case classes as they might have wrong equals/hashcode.
This works for non-generic version of serializer but I am afraid this might not be possible to make it generic
abstract class EnumBase {
def code: String
}
abstract class EnumObject[EnumT <: EnumBase] {}
sealed abstract case class StateCode(code: String) extends EnumBase
object StateCode extends EnumObject[StateCode] {
val CO = new StateCode("CO") {}
val CA = new StateCode("CA") {}
}
case class ClassWithStateCode(id: Int, x: StateCode)
case class MyClass(id: Int, version: Int, year: Int, co: StateCode)
object StateCodeSerializer extends org.json4s.Serializer[StateCode] {
override def deserialize(implicit format: Formats):
PartialFunction[(TypeInfo, JValue), StateCode] = {
case (TypeInfo(EnumerationClass, _), jsonx) => jsonx match {
case i: JString => new StateCode(i.values) {}
}
}
override def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
case i: StateCode => JString(i.code)
}
}
implicit val formats2 = DefaultFormats + StateCodeSerializer
val testClass = MyClass(2, 0, 1966, StateCode.CO)
println(write(testClass))
println(read[MyClass](write(testClass)))
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
I have two case classes and a trait in the following formats:
trait Parent
case class ChildClassOne(kind: String = "first_type", id: String) extends Parent
case class ChildClassTwo(kind: String = "second_type", id: String) extends Parent
And another case class which contains a list of Parents:
case class ParentResponse(total: Int, results: List[Parent])
Basically the json response might have a list of objects which can either be of type ChildClassOne or ChildClassTwo.
Because of this (I think) I need to create a custom serializer:
class ParentSerializer extends CustomSerializer[Parent](format => ( {
case JObject(List(JField("kind", JString(kind)), JField("id", JString(id))))
if kind == "first_type" => ChildClassOne(kind, id)
case JObject(List(JField("kind", JString(kind)), JField("id", JString(id))))
if kind == "second_type" => ChildClassTwo(kind, id)
}, {
case _ => null
}))
This works fine. Problem is that these objects might get quite big and I don't want to specify every single field in custom serializer. I'm also not modifying the properties in any way, and am using the custom serializer just to return the right type of case class based on the kind field.
Is there any way to avoid specifying every single field in JObject and just have the non-custom serializer take care of creating the right case class? eg.
case JObject(List(JField("kind", JString(kind))))
if kind == "first_type" => read[ChildClassOne](format)
You don't need a custom serializer but you need some custom TypeHints to specify the mapping between your custom "kind" field and the class of the object.
trait Parent
case class ChildClassOne(kind: String = "first_type", id: String) extends Parent
case class ChildClassTwo(kind: String = "second_type", id: String) extends Parent
case class ParentResponse(total: Int, results: List[Parent])
object MyTypeHints extends TypeHints {
// map class to kind and viceversa
val classToHint: Map[Class[_], String] = Map (
classOf[ChildClassOne] -> "first_type",
classOf[ChildClassTwo] -> "second_type"
)
val hintToClass = classToHint.map(_.swap)
override val hints: List[Class[_]] = List(classOf[ChildClassOne], classOf[ChildClassTwo])
override def classFor(hint: String): Option[Class[_]] = hintToClass.get(hint)
override def hintFor(clazz: Class[_]): String = classToHint(clazz)
}
implicit val formats = Serialization.formats(MyTypeHints).withTypeHintFieldName("kind")
val obj = ParentResponse(2, List(ChildClassOne(id = "one"), ChildClassTwo(id = "two")))
val serialized = Serialization.write(obj)
val deserializedFromString = Serialization.read[ParentResponse](
"""{"total":2,"results":[{"kind":"first_type","kind":"first_type","id":"one"},
{"kind":"second_type","kind":"second_type","id":"two"}]}""")
val deserializedFromSerialized = Serialization.read[ParentResponse](serialized)
assert(obj == deserializedFromString)
assert(obj == deserializedFromSerialized)
If you don't need to customize the type hint field, you can use the default ones. Search for Serializing polymorphic Lists in the readme
I managed to solve the problem using a Serializer in the end:
trait Parent
case class ChildClassOne(kind: String = "first_type", id: String) extends Parent
case class ChildClassTwo(kind: String = "second_type", id: String) extends Parent
case class ParentResponse(total: Int, results: List[Parent])
class ParentSerializer extends Serializer[Parent] {
private val ParentClass = classOf[Parent]
implicit val formats = DefaultFormats
def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), Parent] = {
case (TypeInfo(ParentClass, _), json) => json match {
case JObject(JField("kind", JString(kind)) :: _) => kind match {
case "first_type" => json.extract[ChildClassOne]
case "second_type" => json.extract[ChildClassTwo]
}
case _ => throw new MappingException("Invalid kind")
}
}
def serialize(implicit format: Formats): PartialFunction[Any, JValue] = Map()
}
implicit val formats = DefaultFormats + new ParentSerializer
I'm generating JSON for a speed where the units may vary. I have a SpeedUnit trait and classes that extend it (Knots, MetersPerSecond, MilesPerHour). The JSON Play documentation said "To convert your own models to JsValues, you must define implicit Writes converters and provide them in scope." I got that to work in most places but not when I had a class extending a trait. What am I doing wrong? Or is there an Enum variant I could or should have used instead?
// Type mismatch: found (String, SpeedUnit), required (String, Json.JsValueWrapper)
// at 4th line from bottom: "speedunit" -> unit
import play.api.libs.json._
trait SpeedUnit {
// I added this to SpeedUnit thinking it might help, but it didn't.
implicit val speedUnitWrites = new Writes[SpeedUnit] {
def writes(x: SpeedUnit) = Json.toJson("UnspecifiedSpeedUnit")
}
}
class Knots extends SpeedUnit {
implicit val knotsWrites = new Writes[Knots] {
def writes(x: Knots) = Json.toJson("KT")
}
}
class MetersPerSecond extends SpeedUnit {
implicit val metersPerSecondWrites = new Writes[MetersPerSecond] {
def writes(x: MetersPerSecond) = Json.toJson("MPS")
}
}
class MilesPerHour extends SpeedUnit {
implicit val milesPerHourWrites = new Writes[MilesPerHour] {
def writes(x: MilesPerHour) = Json.toJson("MPH")
}
}
// ...
class Speed(val value: Int, val unit: SpeedUnit) {
implicit val speedWrites = new Writes[Speed] {
def writes(x: Speed) = Json.obj(
"value" -> value,
"speedUnit" -> unit // THIS LINE DOES NOT TYPE-CHECK
)
}
}
Writes is an example of a type class, which means you need a single instance of a Writes[A] for a given A, not for every A instance. If you're coming from a Java background, think Comparator instead of Comparable.
import play.api.libs.json._
sealed trait SpeedUnit
case object Knots extends SpeedUnit
case object MetersPerSecond extends SpeedUnit
case object MilesPerHour extends SpeedUnit
object SpeedUnit {
implicit val speedUnitWrites: Writes[SpeedUnit] = new Writes[SpeedUnit] {
def writes(x: SpeedUnit) = Json.toJson(
x match {
case Knots => "KTS"
case MetersPerSecond => "MPS"
case MilesPerHour => "MPH"
}
)
}
}
case class Speed(value: Int, unit: SpeedUnit)
object Speed {
implicit val speedWrites: Writes[Speed] = new Writes[Speed] {
def writes(x: Speed) = Json.obj(
"value" -> x.value,
"speedUnit" -> x.unit
)
}
}
And then:
scala> Json.toJson(Speed(10, MilesPerHour))
res0: play.api.libs.json.JsValue = {"value":10,"speedUnit":"MPH"}
I've put the Writes instances in the companion objects for the two types, but they can go elsewhere (if you don't want to mix up serialization concerns in your model, for example).
You can also simplify (or at least concise-ify) this a lot with Play JSON's functional API:
sealed trait SpeedUnit
case object Knots extends SpeedUnit
case object MetersPerSecond extends SpeedUnit
case object MilesPerHour extends SpeedUnit
case class Speed(value: Int, unit: SpeedUnit)
import play.api.libs.json._
import play.api.libs.functional.syntax._
implicit val speedWrites: Writes[Speed] = (
(__ \ 'value).write[Int] and
(__ \ 'speedUnit).write[String].contramap[SpeedUnit] {
case Knots => "KTS"
case MetersPerSecond => "MPS"
case MilesPerHour => "MPH"
}
)(unlift(Speed.unapply))
Which approach you take (functional or explicit) is largely a matter of taste.