json4s and scala: creating custom serializer without specifying all fields - json

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

Related

Extracting a case class with an upper bound

I want to extract a case class from a JSON String, and reuse the code for every class.
Something like this question would have been perfect. But this means that I have to write for every class I want to extract.
I was hoping to do something like:
abstract class SocialMonitorParser[C <: SocialMonitorData] extends Serializable {
def toJSON(socialMonitorData: C): String = {
Requirements.notNull(socialMonitorData, "This field cannot be NULL!")
implicit val formats = DefaultFormats
write(socialMonitorData)
}
def fromJSON(json: String): Option[C] = {
implicit val formats = DefaultFormats // Brings in default date formats etc.
val jsonObj = liftweb.json.parse(json)
try {
val socialData = jsonObj.extract[C]
Some(socialData)
} catch {
case e: Exception => {
Logger.get(this.getClass.getName).warn("Unable to parse the following JSON:\n" + json + "\nException:\n" + e.toString())
None
}
}
}
}
But it gives me the following error:
Error:(43, 39) No Manifest available for C.
val socialData = jsonObj.extract[C]
Error:(43, 39) not enough arguments for method extract: (implicit formats: net.liftweb.json.Formats, implicit mf: scala.reflect.Manifest[C])C.
Unspecified value parameter mf.
val socialData = jsonObj.extract[C]
I was hoping I could do something like this, and maybe there is a way. But I can't wrap my head around this.
I will try to extend the question with some other information. Supposing I have Twitter and Facebook data, in case class like these:
case class FacebookData(raw_data: String, id: String, social: String) extends SocialMonitorData
case class TwitterData(...) extends SocialMonitorData{ ...}
I wish I could reuse the fromJSON and toJSON just once passing the Upper Bound type
class TwitterParser extends SocialMonitorParser[TwitterData] {
override FromJSON}
class FacebookParser extends SocialMonitorParser[FacebookData]
Much obliged.
I'm not sure why you want SocialMonitorParser to be abstract or Serializable or how are you going to use it but if you look closer at the error you may see that the compilers wants a Manifest for C. Manifest is a Scala way to preserve type information through the type erasure enforced onto generics by JVM. And if you fix that, then the code like this compiles:
import net.liftweb.json._
import net.liftweb.json.Serialization._
trait SocialMonitorData
case class FacebookData(raw_data: String, id: String, social: String) extends SocialMonitorData
class SocialMonitorParser[C <: SocialMonitorData : Manifest] extends Serializable {
def toJSON(socialMonitorData: C): String = {
// Requirements.notNull(socialMonitorData, "This field cannot be NULL!")
implicit val formats = DefaultFormats
write(socialMonitorData)
}
def fromJSON(json: String): Option[C] = {
implicit val formats = DefaultFormats // Brings in default date formats etc.
val jsonObj = parse(json)
try {
val socialData = jsonObj.extract[C]
Some(socialData)
} catch {
case e: Exception => {
// Logger.get(this.getClass.getName).warn("Unable to parse the following JSON:\n" + json + "\nException:\n" + e.toString())
None
}
}
}
}
and you can use it as
def test(): Unit = {
val parser = new SocialMonitorParser[FacebookData]
val src = FacebookData("fb_raw_data", "fb_id", "fb_social")
println(s"src = $src")
val json = parser.toJSON(src)
println(s"json = $json")
val back = parser.fromJSON(json)
println(s"back = $back")
}
to get the output exactly as one would expect.

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 :-) )

Trouble Serializing Enums with JSON4S

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)))

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.