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)))
Related
I am using Jackson scala module and I need to serialize/deserialize case object enumerations. The serialization works fine, but when deserializing back the values I get a com.fasterxml.jackson.databind.exc.InvalidDefinitionException. There is a way to make it work? I would like to avoid using "classic" scala enumerations, since I would lose the ability to define enumeration hierarchies.
Code used for the tests:
import com.fasterxml.jackson.annotation.JsonValue
import com.fasterxml.jackson.databind.{DeserializationFeature, ObjectMapper}
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
sealed trait Operations { #JsonValue def jsonValue: String }
case object Foo extends Operations { override def jsonValue: String = "Foo" }
case object Bar extends Operations { override def jsonValue: String = "Bar" }
sealed trait RichOperations extends Operations
case object Baz extends RichOperations { override def jsonValue: String = "Baz" }
object JsonUtils extends App {
val jsonMapper = new ObjectMapper() with ScalaObjectMapper
jsonMapper.registerModule(DefaultScalaModule)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
def toJson(value: Any): String = jsonMapper.writeValueAsString(value)
def fromJson[T: Manifest](json: String): T = jsonMapper.readValue[T](json)
println(toJson(Foo)) // "Foo"
println(toJson(Bar)) // "Bar"
println(toJson(Baz)) // "Baz"
println(fromJson[Operations](toJson(Foo))) // throws InvalidDefinitionException
println(fromJson[Operations](toJson(Bar)))
println(fromJson[RichOperations](toJson(Baz)))
}
I solved the problem defining custom deserializers.
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.{DeserializationContext, JsonDeserializer}
class OperationsDeserializer extends JsonDeserializer[Operations] {
override def deserialize(p: JsonParser, ctxt: DeserializationContext): Operations = {
p.getValueAsString match {
case Foo.jsonValue => Foo
case Bar.jsonValue => Bar
case value => throw new IllegalArgumentException(s"Undefined deserializer for value: $value")
}
}
}
class RichOperationsDeserializer extends JsonDeserializer[Operations] {
override def deserialize(p: JsonParser, ctxt: DeserializationContext): Operations = {
p.getValueAsString match {
case Foo.jsonValue => Foo
case Bar.jsonValue => Bar
case Baz.jsonValue => Baz
case value => throw new IllegalArgumentException(s"Undefined deserializer for value: $value")
}
}
}
Moreover, since stable identifiers are required in match/case, I changed jsonValue from def to val: this unfortunately requires the declaration of #JsonValue annotation in each case object enumeration.
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
#JsonDeserialize(using = classOf[OperationsDeserializer])
sealed trait Operations { val jsonValue: String }
case object Foo extends Operations { #JsonValue override val jsonValue: String = "Foo" }
case object Bar extends Operations { #JsonValue override val jsonValue: String = "Bar" }
#JsonDeserialize(using = classOf[RichOperationsDeserializer])
sealed trait RichOperations extends Operations
case object Baz extends RichOperations { #JsonValue override val jsonValue: String = "Baz" }
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 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 :-) )
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
Currently I am working a web application using the Play Framework and now I am working on a JSON api. Unfortunately I have problems with parsing my objects to JSON with the built in JSON library. We have the following trait, which defines the type of Shipment and which parser to use. And a case class which has a ShipmentType so we know which parser to user for each type. And there is a method which returns all stored shipments as a list.
trait ShipmentType {
def parser(list: List[String]): ShipmentTypeParser
}
object ShipmentTypeA extends ShipmentType {
def parser(list: List[String]) = new ShipmentTypeAParser(list)
}
object ShipmentTypeB extends ShipmentType {
def parser(list: List[String]) = new ShipmentTypeBParser(list)
}
object ShipmentTypeC extends ShipmentType {
def parser(list: List[String]) = new ShipmentTypeCParser(list)
}
case class Shipment(id: Long, name: String, date: Date, shipmentType: Type)
To write this JSON I use the following implicit val:
import play.api.libs.json._
import play.api.libs.functional.syntax._
def findAll = Action {
Ok(Json.toJson(Shipments.all))
}
implicit val shipmentWrites: Writes[Shipment] = (
(JsPath \ "id").write[Option[Long]] and
(JsPath \ "name").write[String] and
(JsPath \ "date").write[Date] and
(JsPath \ "shipmentType").write[ShipmentType]
)(unlift(Shipment.unapply))
Next we need an extra one for the ShipmentType:
implicit val shipmentTypeWriter: Writes[ShipmentType] = ()
But there is where I get stuck, I cannot seem to find a way how to define the writer for the ShipmentType.
I also tried defining them as follows according to another page of the Play Framework Documentation:
implicit val shipmentWrites: Writes[Shipment] = Json.writes[Shipment]
implicit val shipmentTypeWrites: Writes[ShipmentType] =Json.writes[ShipmentType]
However this fails too, as I get errors like: "No unapply function found".
Anyone an idee how to implement a Writer for this? Preferably in the form of a string in json.
I create working example:
trait ShipmentTypeParser
class ShipmentTypeAParser(list: List[String]) extends ShipmentTypeParser
class ShipmentTypeBParser(list: List[String]) extends ShipmentTypeParser
object ShipmentTypeA extends ShipmentType {
override def parser(list: List[String]) = new ShipmentTypeAParser(list)
}
object ShipmentTypeB extends ShipmentType {
override def parser(list: List[String]) = new ShipmentTypeBParser(list)
}
object Models {
implicit val shipmentTypeWrites = new Format[ShipmentType] {
override def writes(shipmentType: ShipmentType): JsValue =
JsString(shipmentType.getClass.getName)
override def reads(json: JsValue): JsResult[ShipmentType] = json match {
case JsString(className) =>
Try(Class.forName(className)) match {
case Success(c) =>
JsSuccess(c.getField("MODULE$").get(c).asInstanceOf[ShipmentType])
case Failure(th) =>
JsError(th.getMessage)
}
case _ =>
JsError(json.toString)
}
}
implicit val shipmentWrites: Format[Shipment] = Json.format[Shipment]
}
trait ShipmentType {
def parser(list: List[String]): ShipmentTypeParser
}
case class Shipment(id: Long, name: String, date: Date, shipmentType: ShipmentType)
And test in scalatest:
class JsonSpec extends FlatSpec with Matchers {
"Shipment " should " be convert to json and from " in {
import Models._
val shipment = Shipment(3, "33", new Date(), ShipmentTypeA)
val jsonShipment = Json.toJson(shipment)
println(jsonShipment)
val fromJson = Json.fromJson[Shipment](jsonShipment)
fromJson match{
case JsSuccess(shipmentFromJson,_) =>
shipmentFromJson shouldBe shipment
case _ =>
fail(fromJson.toString)
}
}
}