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.
Related
Similar to this, instead of wanting to accept one of several unrelated classes I'd like to return one.
I have an orchestrating service that utilizes several underlying Repositories. Each repo can pass back an error. However, these Error classes do not share a common ancestor.
For instance:
case class DistributionError
case class UserError
case class ContentError
I wanted to create my orchestrating service method like so:
def doSomethingComplicated(): Either[TheErrors, Boolean] = {
//...
case Failure(e) => Left(new DistributionError)
//...
}
Then I would invoke it like so:
doSomethingComplicated() match {
case Left(error) => {
error match {
case _: DistributionError => ...
case _: UserError => ...
case _: ContentError => ...
}
}
case Right(success) => ...
}
As per the linked SO answer, I tried:
class TheErrors[T]
object TheErrors {
implicit object DistWitness extends TheErrors[DistributionError]
implicit object UserWitness extends TheErrors[UserError]
implicit object ContentWitness extends TheErrors[ContentError]
}
But it just won't work the same way it does for parameters. The compiler always complains with:
> Error:(176, 48) type mismatch;
> found : UserError
> required: T
> case None => Left(UserError)
Is it even possible to use this method for return types?
AnyRef solution
The quick & cheap solution would be to drop the whole TheErrors typeclass, and simply return Either[AnyRef, Boolean] from doSomethingComplicated.
Existential types solution
If you absolutely want to ensure that doSomethingComplicated only returns types of errors that have previously been explicitly white-listed in TheErrors companion object, you can do this:
import scala.language.existentials
case class DistributionError()
case class UserError()
case class ContentError()
class TheErrors[T]
object TheErrors {
implicit object DistWitness extends TheErrors[DistributionError]
implicit object UserWitness extends TheErrors[UserError]
implicit object ContentWitness extends TheErrors[ContentError]
}
def allowedError[E](e: E)(implicit witness: TheErrors[E])
: (E, TheErrors[E]) = (e, witness)
type AllowedError = (E, TheErrors[E]) forSome { type E }
def doSomethingComplicated(): Either[AllowedError, Boolean] = {
import TheErrors._
/* sth complicated */ Left(allowedError(DistributionError()))
}
doSomethingComplicated() match {
case Left((error, _)) => {
error match {
case _: DistributionError => 42
case _: UserError => 58
case _: ContentError => 100
}
}
case Right(success) => 2345678
}
Essentially, all it does is checking for an existence of a TheErrors-witness when you call allowedError, and attaching the witness to the error. This makes sure that only the errors for which the witnesses can be found are returned from doSomethingComplicated. Note however, that it does not help you to check the exhaustiveness of the pattern matching. For this, you would have to take the usual path, and wrap all your errors into subclasses of one common sealed trait.
Sealed trait solution
import scala.language.implicitConversions
case class DistributionError()
case class UserError()
case class ContentError()
sealed trait TheErrors
case class Distr(e: DistributionError) extends TheErrors
case class User(e: UserError) extends TheErrors
case class Content(e: ContentError) extends TheErrors
object TheErrors {
implicit def apply(d: DistributionError): TheErrors = Distr(d)
implicit def apply(d: UserError): TheErrors = User(d)
implicit def apply(d: ContentError): TheErrors = Content(d)
}
def doSomethingComplicated(): Either[TheErrors, Boolean] = {
/* sth complicated */ Left(DistributionError())
}
doSomethingComplicated() match {
case Left(error) => {
error match {
case Distr(e) => 42
case User(e) => 58
case Content(e) => 100
}
}
case Right(success) => 2345678
}
Implicit conversion + plain old subclass polymorphism
With implicit conversions and good old subclass polymorphism, you can get rid of any specific TheErrors subclasses in both doSomethingComplicated and in the caller code:
import scala.language.implicitConversions
case class DistributionError()
case class UserError()
case class ContentError()
sealed trait TheErrors {
def error: AnyRef
}
object TheErrors {
private case class TheError(val error: AnyRef) extends TheErrors
implicit def apply(d: DistributionError): TheErrors = TheError(d)
implicit def apply(d: UserError): TheErrors = TheError(d)
implicit def apply(d: ContentError): TheErrors = TheError(d)
}
def doSomethingComplicated(): Either[TheErrors, Boolean] = {
/* sth complicated */ Left(DistributionError())
}
doSomethingComplicated() match {
case Left(e) => {
e.error match {
case _: DistributionError => 42
case _: UserError => 58
case _: ContentError => 100
}
}
case Right(success) => 2345678
}
Few options:
Use Shapeless's Coproduct type
Use Any
Nested Either i.e. Either[Either[Either[A, B], C], D]
Define your own sealed trait with subclasses that just wrap their corresponding types i.e. class UserErrorWrapper(val u: UserError) extends MyErrorTrait
Wait for Dotty and use a union type
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)
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
}
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)
Suppose I have an enumeration or sealed group of case objects as follows:
sealed abstract class Status
case object Complete extends Status
case object Failed extends Status
case object Pending extends Status
case object Unknown extends Status
or
object Status extends Enumeration {
val Complete, Failed, Pending, Unknown = Value
}
What is the easiest way to create json formats for these so that I can very easily (programmatically) generate json formats for use in a custom JsonFormat factory method, such as the following, which works for all normal case classes, strings, collections, etc., but produces {} or {"name": null} for the above two types of enumerations?:
import org.json4s.DefaultFormats
import org.json4s.jackson.JsonMethods.parse
import org.json4s.jackson.Serialization
import org.json4s.jvalue2extractable
import org.json4s.string2JsonInput
trait JsonFormat[T] {
def read(json: String): T
def write(t: T): String
}
object JsonFormat {
implicit lazy val formats = DefaultFormats
def create[T <: AnyRef: Manifest](): JsonFormat[T] = new JsonFormat[T] {
def read(json: String): T = parse(json).extract[T]
def write(t: T): String = Serialization.write(t)
}
}
We've used org.json4s.ext.EnumNameSerializer to serialize enumerations:
import org.json4s._
import org.json4s.ext.EnumNameSerializer
class DoesSomething {
implicit lazy val formats = DefaultFormats + new EnumNameSerializer(Status)
...stuff requiring serialization or deserialization...
}
In practice we have mixin trait that adds the implicit format and defines all of our custom serializer/desrializers:
trait OurFormaters extends Json4sJacksonSupport {
implicit lazy val json4sJacksonFormats:Formats = DefaultFormats +
UuidSerializer +
new EnumNameSerializer(Status) +
...
}
object UuidSerializer extends CustomSerializer[UUID](format =>
(
{
case JString(s) => UUID.fromString(s)
case JNull => null
},
{
case x: UUID => JString(x.toString)
}
)
)