Fail-safe wrapper for spary-json parseJson func - scala

I want to write fail-safe wrapper for spary-json str.parseJson.convertTo[A].
It must have logic - "when I can't parse json as case class A, I try parse it as case class Error"
def parse(str:String) =
try {
str.parseJson.convertTo[A]
} catch {
case e:Exception => str.parseJson.convertTo[Error]
}
but also I want to make class A a parameter.
def parse[A<:Obj](str:String):Obj = {
import JsonProtocols._
try {
str.parseJson.convertTo[A]
} catch {
case e:Exception => str.parseJson.convertTo[Error]
}
}
using:
...
trait Obj
case class Error(error:String) extends Obj
case class DataA(a1:String, a2: Int) extends Obj
case class DataB(b1:String, b2: Boolean) extends Obj
object JsonProtocols extends DefaultJsonProtocol {
implicit val errorFormat = jsonFormat1(Error)
implicit val dataAFormat = jsonFormat2(DataA)
implicit val dataBFormat = jsonFormat2(DataB)
...
}
...
parse[DataA]("...json...") match {
case obj: DataA => "..."
case obj: Error => "..."
}
...
I get compiling error:
Error:(25, 30) Cannot find JsonReader or JsonFormat type class for A
str.parseJson.convertTo[A]
^
How can I fix this error?
Can I do this by another way?

Simplifying things, looks like that you've:
defined 3 case classes with appropriate JsonReaders
defined a generic function, which type is lower bound to Obj.
The compiler tells you that it cannot find a JsonReader for all possible classes implementing trait Obj, because you have defined only specific JsonReaders for Error, DataA and DataB.
To solve the problem you can use Either[T,Error] type for deserialization like:
sealed trait Obj
case class Error(error:String) extends Obj
case class DataA(a1:String, a2: Int) extends Obj
case class DataB(b1:String, b2: Boolean) extends Obj
val strA = """{"a1":"foo", "a2": 1}"""
val strB = """{"b1":"bar", "b2": false}"""
val srtE = """{"error": "oops"}"""
object JsonProtocols extends DefaultJsonProtocol {
implicit val errorFormat = jsonFormat1(Error)
implicit val dataAFormat = jsonFormat2(DataA)
implicit val dataBFormat = jsonFormat2(DataB)
}
import JsonProtocols._
val result:Obj = strA.parseJson.convertTo[Either[DataA,Error]] match {
case Left(dataA) => dataA
case Right(error) => error
}

Related

Create object at runtime which have the same internal contents

I am performing a repetitive task of creating a object that has same internal contents and hence thought of creating a generic method that would help me achieve this.
The internal object is as follows
case class Data(value: Int)
I have a base trait as follows
trait Base
There are a couple of classes that extend this trait
case class A(data: Data) extends Base
case class B(data: Data) extends Base
case class C(data: Data) extends Base
The generic method that I am trying to write to create the object
def getObject[T <: Base](data: Data, t: T) = {
T(data)
}
However, while trying to do so, I get a compile-time error saying that
Cannot resolve symbol T
Can you please let me know what I am missing in this method implementation.
Note:- This is a very simplistic implementation of what I am trying to do in my code.
Due to type-erasure, you can't use generic type to create a new instance of an object.
You could use ClassTag to capture a class of T at runtime:
case class Data(value: Int)
trait Base
case class A(data: Data) extends Base
case class B(data: Data) extends Base
case class C(data: Data) extends Base
def getObject[T <: Base](data: Data)(implicit ct: ClassTag[T]): T =
ct.runtimeClass.getDeclaredConstructors.head.newInstance(data).asInstanceOf[T]
val a: A = getObject[A](Data(1))
val b: B = getObject[B](Data(2))
val c: C = getObject[C](Data(3))
As cchantep noticed, it has a drawback, that if your case class doesn't have a constructor with single argument Data, this function will fail at runtime.
Consider typeclass solution for compile-time safety
final case class Data(value: Int)
final case class A(data: Data)
final case class B(data: Data)
final case class C(data: Data)
trait BaseFactory[T] {
def apply(data: Data): T
}
object BaseFactory {
def apply[T](data: Data)(implicit ev: BaseFactory[T]): T = ev.apply(data)
implicit val aBaseFactory: BaseFactory[A] = (data: Data) => A(data)
implicit val bBaseFactory: BaseFactory[B] = (data: Data) => B(data)
implicit val cBaseFactory: BaseFactory[C] = (data: Data) => C(data)
}
val data = Data(42)
BaseFactory[A](data) // res0: A = A(Data(42))
BaseFactory[B](data) // res1: B = B(Data(42))
BaseFactory[C](data) // res2: C = C(Data(42))

Type inference of implicit conversions

Consider this code:
sealed trait Data
case class StringData(string: String) extends Data
case class IntData(int: Int) extends Data
trait Reader[A] {
def read(data: Data): A
}
implicit val stringReader: Reader[String] = {
case StringData(string) => string
case _ => sys.error("not a string")
}
implicit val intReader: Reader[Int] = {
case IntData(int) => int
case _ => sys.error("not an int")
}
With this in scope, I want to write an implicit method that silently converts from Data values to their "real" Scala values.
implicit def fromData[A: Reader](data: Data): A =
implicitly[Reader[A]].read(data)
But then, this code does not compile:
val str: String = StringData("foo")
val int: Int = IntData(420)
The error is a type mismatch. Standard debugging methods for implicits show that the A from fromData can't be infered (all implicit Readers are shown as applicable).
For your convenience, this is a link to a scastie of the code. In this other scastie, a similiar, yet different, and working snippet is presented.
My question: What is going on here?
Making the change to your Data class as well as your reader implicit conversion as below allows the code to compile.
import scala.language.implicitConversions
sealed trait Data[A]
case class StringData(string: String) extends Data[String]
case class IntData(int: Int) extends Data[Int]
trait Reader[A] {
def read(data: Data[A]): A
}
implicit val stringReader: Reader[String] = {
case StringData(string) => string
case _ => sys.error("not a string")
}
implicit val intReader: Reader[Int] = {
case IntData(int) => int
case _ => sys.error("not an int")
}
implicit def fromData[A](data: Data[A])(implicit ev: Reader[A]): A = ev.read(data)
val str: String = StringData("foo")
val int: Int = IntData(420)
You need data to be typed so that the compiler can infer which Reader should be used based on the type parameter of Data. Also notice that the following would not compile if you did not define an implicit Reader.
case class DoubleData(d: Double) extends Data[Double]
// Fails compilation because no implicit Reader exists.
val d: Double = DoubleData(5d)

spray-json serializing inheritance case class

I have following class structure which involves inheritance
sealed trait GeometryObject
case class Point(coordinates: Array[Double],`type` :GeometryObjectsType) extends GeometryObject
case class Polygon(coordinates: Array[Array[Array[Double]]],`type` :GeometryObjectsType) extends GeometryObject
My protocol looks as follows:
object GeoJsonProtocol extends DefaultJsonProtocol {
implicit val geometryObjectsTypeFormat = GeometryObjectsTypeFormat
implicit val polygonFormat = jsonFormat2(Polygon)
implicit val pointFormat = jsonFormat2(Point)
}
Getting an error - could not find implicit value for evidence parameter of type GeometryObject.
Is there a way how to deal with that and keep inheritance flexibility?
There is a need for class hierarchy parent format as follows:
object GeometryObjectLeafFormats extends DefaultJsonProtocol{
implicit val geometryObjectsTypeFormat = GeometryObjectsTypeFormat
implicit val polygonFormat = jsonFormat2(Polygon)
implicit val pointFormat = jsonFormat2(Point)
}
object GeometryObjectFormat extends JsonFormat[GeometryObject] {
import GeometryObjectLeafFormats._
override def read(json: JsValue): GeometryObject = json match {
case known:JsObject if known.fields.contains("type") =>
known.fields.get("type").get match{
case JsString(PointType.value) => pointFormat.read(known)
case JsString(PolygonType.value) => polygonFormat.read(known)
case unknown => deserializationError(s"unknown GeometryObject: ${unknown}")
}
case unknown => deserializationError(s"unknown GeometryObject: ${unknown}")
}
override def write(obj: GeometryObject): JsValue = obj match {
case x:Point => pointFormat.write(x)
case x:Polygon=> polygonFormat.write(x)
case unrecognized => serializationError(s"Serialization problem ${unrecognized}")
}
}
Note: I have separate class hierarchy denoting the type which is serialized to type field which is mandatory in order to make class hierarchy serialization working.

spray-json serialization in spray-routing with custom JsonFormats

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)

How to extend an abstract class in scala and use the abstract constructor

I have abstract class A
abstract class A{
def this(obj:Object){
this()
obj match{
case s:String => stringMethod(s)
case n:Int => intMethod(n)
}
def stringMethod(s:String)
def intMethod(n:Int)
}
and I have a class that extends this class
class B(obj:Object) extends A(obj){
var s:String = null
def stringMethod(s:String){
this.s = s
}
def intMethod(n:Int){
this.s = n.toString
}
}
The point of this class is to instantiate an object and its variables depending on the class type of the object used to instantiate it but the problem is that when the abstract constructor is called, the default constructor of object which is extending the abstract object is somehow being called after. This alters the value of var s back to null.
This a really simple implementation of my classes and I have more variables in class B and more logic inside intMethod and stringMethod.
I realize this might be a completely wrong way of doing this, so if there is a better way please let me know.
The body of a super class is always executed before the body of a sub class. In your case A calls stringMethod or intMethod first, then finally B's body is executed, assign null to s. If you remove that assignment, it should work:
abstract class A{
def this(obj:Object){
this()
obj match{
case s:String => stringMethod(s)
case n:Int => intMethod(n)
}
}
def stringMethod(s:String)
def intMethod(n:Int)
}
class B(obj:Object) extends A(obj){
var s:String = _ // !
def stringMethod(s:String){
this.s = s
}
def intMethod(n:Int){
this.s = n.toString
}
}
val b = new B("foo")
b.s
Neverless, the style is problematic. Here are two alternatives:
trait A {
def obj: Any
def s: String
}
class B(val obj: Any) extends A {
val s = obj match {
case i: Int => i.toString
case x: String => x
case x => throw new IllegalArgumentException(x.toString)
}
}
Or better statically checked:
object B {
def apply(i: Int ): B = new B(i, i.toString)
def apply(s: String): B = new B(s, s)
}
class B private(val obj: Any, val s: String) extends A
B(1.0) // not allowed