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)
}
)
)
Related
I have this minimal example, I want to create encoders/decoders with circe semi-automatic derivation for the generic case class A[T]
import io.circe.{Decoder, Encoder}
import io.circe.generic.semiauto._
import io.circe.syntax._
sealed trait MyTrait
object MyTrait {
implicit val encoder: Encoder[MyTrait] = deriveEncoder
implicit val decoder: Decoder[MyTrait] = deriveDecoder
}
case class A[T](value: T) extends MyTrait
object A {
implicit def encoder[T: Encoder]: Encoder[A[T]] = deriveEncoder
implicit def decoder[T: Decoder]: Decoder[A[T]] = deriveDecoder
}
This codes does not compile and instead outputs this error
could not find Lazy implicit value of type io.circe.generic.encoding.DerivedAsObjectEncoder[A]
And the same for the decoder
What Am I doing wrong here and how can I get it working?
There are few issues. One is that MyTrait Encoder/Decoder will try to dispatch encodeing/decoding into codecs for the subtypes - since e thtrait is sealed all possible traits are known, so such list can be obtained by compiler.
BUT
While MyTrait trait does not take type parameters, its only implementation A takes. Which basically turns it into an existential type.
val myTrait: MyTrait = A(10)
myTrait match {
case A(x) =>
// x is of unknown type, at best you could use runtime reflection
// but codecs are generated with compile time reflection
}
Even if you wanted to make these codecs manually, you have no way of doing it
object Scope {
def aEncoder[T: Encoder]: Encoder[A[T]] = ...
val myTraitEncoder: Encoder[MyTrait] = {
case A(value) =>
// value is of unknown type, how to decide what Encoder[T]
// pass into aEncoder?
}
}
For similar reasons you couldn't manually implement Decoder.
To be able to implement codec manually (which is kind of prerequisite to being able to generate it), you can only remove type parameters as you go into subclasses, never add them.
sealed trait MyTrait[T]
case class A[T](value: T) extends MyTrait[T]
This would make it possible to know what kind of T is inside A since it would be passed from MyTrait, so you could write your codec manually and they would work.
Another problem is that reliable generation of ADT's usually require some configuration, e.g. whether or not to use discimination field. And that is provided by circe-generic-extras. Once you use it, (semi)automatic derivation is possible:
import io.circe.{Decoder, Encoder}
import io.circe.generic.extras.Configuration
import io.circe.generic.extras.semiauto._
import io.circe.syntax._
// can be used to tweak e.g. discriminator field name
implicit val config: Configuration = Configuration.default
sealed trait MyTrait[T]
object MyTrait {
implicit def encoder[T: Encoder]: Encoder[MyTrait[A]] = deriveEncoder
implicit def decoder[T: Decoder]: Decoder[MyTrait[A]] = deriveDecoder
}
case class A[T](value: T) extends MyTrait[T]
object A {
implicit def encoder[T: Encoder]: Encoder[A[T]] = deriveEncoder
implicit def decoder[T: Decoder]: Decoder[A[T]] = deriveDecoder
}
Among other solutions to the problem, if you really didn't want to use type parameter in MyTrait but have polymorphism in A would be to replace umbound, generic A with another ADT (or sum type in Scala 3), so that list of all possible implementations would be known and enumerated.
// x is of unknown type, at best you could use runtime reflection
I'll just comment that in principle you can define Decoder with runtime reflection (runtime compilation) based on the class of value in case class A[T](value: T)
sealed trait MyTrait
case class A[T](value: T) extends MyTrait
object A {
// custom codecs
implicit val intEnc: Encoder[A[Int]] = new Encoder[A[Int]] {
override def apply(a: A[Int]): Json = Json.obj("A" -> Json.obj("value" -> Json.fromInt(a.value)))
}
implicit val strEnc: Encoder[A[String]] = new Encoder[A[String]] {
override def apply(a: A[String]): Json = Json.obj("value" -> Json.fromString(a.value))
}
implicit val boolEnc: Encoder[A[Boolean]] = new Encoder[A[Boolean]] {
override def apply(a: A[Boolean]): Json = Json.fromBoolean(a.value)
}
}
val Integer = classOf[Integer]
val Boolean = classOf[java.lang.Boolean]
def primitiveClass[T](runtimeClass: Class[_]): Class[_] = runtimeClass match {
case `Integer` => classOf[Int]
case `Boolean` => classOf[Boolean]
//...
case _ => runtimeClass
}
object MyTrait {
implicit val encoder: Encoder[MyTrait] = new Encoder[MyTrait] {
override def apply(a: MyTrait): Json = a match {
case a1#A(t) =>
tb.eval(q"implicitly[io.circe.Encoder[A[${rm.classSymbol(primitiveClass(t.getClass)).toType}]]]")
.asInstanceOf[Encoder[A[_]]]
.apply(a1)
}
}
}
(A(1): MyTrait).asJson.noSpaces // {"A":{"value":1}}
(A("a"): MyTrait).asJson.noSpaces //{"value":"a"}
(A(true): MyTrait).asJson.noSpaces //true
But for case class A[T](value: Int) such trick would be impossible.
I have class as below
trait RiskCheckStatusCode {
def code: String
def isSuccess: Boolean
}
object RiskCheckStatusCode {
val SUCCESS = SuccessRiskCheckStatusCode("1.1.1")
val FAIL = FailRiskCheckStatusCode("2.2.2")
case class SuccessRiskCheckStatusCode(code: String) extends RiskCheckStatusCode {
override def isSuccess = true
}
object SuccessRiskCheckStatusCode {
import spray.json.DefaultJsonProtocol._
implicit val formatter = jsonFormat1(SuccessRiskCheckStatusCode.apply)
}
case class FailRiskCheckStatusCode(code: String) extends RiskCheckStatusCode {
override def isSuccess = false
}
object FailRiskCheckStatusCode {
import spray.json.DefaultJsonProtocol._
implicit val formatter = jsonFormat1(FailRiskCheckStatusCode.apply)
}
}
and now I would like to convert the list of RiskCheckStatusCode to json
object Main extends App{
import spray.json._
import spray.json.DefaultJsonProtocol._
val l = List(RiskCheckStatusCode.SUCCESS, RiskCheckStatusCode.FAIL)
implicit object RiskCheckStatusCodeJsonFormat extends JsonWriter[RiskCheckStatusCode] {
override def write(obj: RiskCheckStatusCode): JsValue = obj match {
case obj: SuccessRiskCheckStatusCode => obj.toJson
case obj: FailRiskCheckStatusCode => obj.toJson
}
}
def json[T](list: T)(implicit formatter: JsonWriter[T]) = {
print(list.toJson)
}
json(l)
}
but the json method can not find jsonWriter[RiskCheckStatusCode].
Can you explain why? Maybe should I do it differently for trait type?
Edit:
It works for
val l: RiskCheckStatusCode = RiskCheckStatusCode.SUCCESS
so the problem is with List[RiskCheckStatusCode] because I have a formatter for RiskCheckStatusCode, not for List[RiskCheckStatusCode]. I tried import DefaultJsonProtocol but it still does not work.
import spray.json.DefaultJsonProtocol._
I have to change the definitions? From
implicit object RiskCheckStatusCodeJsonFormat extends JsonWriter[RiskCheckStatusCode]
to
implicit object RiskCheckStatusCodeJsonFormat extends JsonWriter[List[RiskCheckStatusCode]]
error:
Error:(28, 7) Cannot find JsonWriter or JsonFormat type class for List[com.example.status.RiskCheckStatusCode]
json(l)
Error:(28, 7) not enough arguments for method json: (implicit formatter: spray.json.JsonWriter[List[com.example.status.RiskCheckStatusCode]])Unit.
Unspecified value parameter formatter.
json(l)
Your code is fine you are just not having toJson in your scope (it is located in the package object of spray.json).
Add it and your code should compile:
object Main extends App with DefaultJsonProtocol {
import spray.json._
// ...
}
Furthermore spray has some issues to lift JsonWriter through derived formats (see this for details).
You can switch to JsonFormat instead:
implicit object RiskCheckStatusCodeJsonFormat extends JsonFormat[RiskCheckStatusCode] {
override def write(obj: RiskCheckStatusCode): JsValue = obj match {
case obj: SuccessRiskCheckStatusCode => obj.toJson
case obj: FailRiskCheckStatusCode => obj.toJson
}
override def read(json: JsValue): RiskCheckStatusCode = ???
}
In addition, to cleanup the type of your List change the definition of RiskCheckStatusCode to (this explains more details):
sealed trait RiskCheckStatusCode extends Serializable with Product
I wanted to serialize and deserialize some case classes and realized I was repeating code. Unfortunately I cannot figure out a way to keep things DRY. Hoping someone can provide some assistance. Below I will provide a sample problem that is not DRY.
Sample Problem
import org.json4s.jackson.Serialization
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import org.json4s.JsonAST.JString
import org.json4s.{CustomSerializer, DefaultFormats}
case class Bar(bar: String, date: ZonedDateTime)
case class Foo(foo: String)
trait JsonParser {
private case object ZDTSerializer extends CustomSerializer[ZonedDateTime](_ => (
{ case JString(s) => ZonedDateTime.parse(s) },
{ case zdt: ZonedDateTime => JString(zdt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"))) }
))
implicit val formats = DefaultFormats + ZDTSerializer
}
object BarParser extends JsonParser {
def deserialize(jsonBar: String): Bar = {
Serialization.read[Bar](jsonBar)
}
def serialize(bar: Bar): String = {
Serialization.write[Bar](bar)
}
}
object FooParser extends JsonParser {
def deserialize(jsonFoo: String): Foo = {
Serialization.read[Foo](jsonFoo)
}
def serialize(foo: Foo): String = {
Serialization.write[Foo](foo)
}
}
object Main {
def main(args: Array[String]): Unit = {
val foo = Foo("foo")
println(FooParser.serialize(foo)) // {"foo":"foo"}
println(FooParser.deserialize(FooParser.serialize(foo))) // Foo(foo)
}
}
Above it is clear that the logic to serialize and deserialize is repeated. This is one of the things I've tried (which doesn't compile).
Attempt to Solve
case class Bar(product: String, date: ZonedDateTime)
case class Foo(title: String)
abstract class GenericJsonParser[T] {
private case object ZDTSerializer extends CustomSerializer[ZonedDateTime](_ => (
{ case JString(s) => ZonedDateTime.parse(s) },
{ case zdt: ZonedDateTime => JString(zdt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"))) }
))
implicit val formats = DefaultFormats + ZDTSerializer
def deserialize(json: String): T = {
Serialization.read[T](json) // No Manifest available for T
}
def serialize(thing: T): String = {
Serialization.write[T](thing) // type arguments [A] conform to the bounds of none of the overloaded alternatives ...
}
}
object BarJsonParser extends GenericJsonParser[Bar]
object FooParser extends GenericJsonParser[Foo]
Any guidance would be appreciated.
I think you can use Json.format[ACaseClass], for example:
import play.api.libs.json.{Format, Json}
case class ACaseClass(value: String, anotherValue: Int)
implicit val formatACaseClass = Json.format[ACaseClass]
I guess for Seralization.read and write you still have to pass an implicit value, jvm need must know how to read/write your object/
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.
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)