Spray json trait formatter with nested subclasses - json

My use-case is the following:
import spray.json._
import DefaultJsonProtocol._
sealed trait TestT
case class A(a: String) extends TestT
case class B(b: String) extends TestT
case class C(c: TestT) extends TestT
case class Parent(list: Seq[TestT])
implicit val aFormat = jsonFormat1(A)
implicit val bFormat = jsonFormat1(B)
implicit lazy val cFormat = lazyFormat(jsonFormat1(C))
implicit object TestTJsonFormat extends RootJsonFormat[TestT] {
override def read(json: JsValue) = {
if(json.asJsObject.fields.get("a").isDefined) {
json.convertTo[A]
} else if(json.asJsObject.fields.get("b").isDefined) {
json.convertTo[B]
} else if(json.asJsObject.fields.get("c").isDefined) {
json.convertTo[C]
} else {
???
}
}
override def write(obj: TestT) = {
obj match {
case a:A => a.toJson
case b:B => b.toJson
case c:C => c.toJson
case _ => ???
}
}
}
implicit val pFormat = jsonFormat1(Parent)
val json = Parent(Seq(A("test"), B("test2"), A("test3"))).toJson
println(json)
val p = json.convertTo[Parent]
println(p)
The main problem is that I can't make a formatter for C if I don't have a formatter for TestT. But I can't make a formatter for TestT because I don't have a json.convertTo for C yet...
Any idea how can I resolve this form of dependency?

https://github.com/milessabin/spray-json-shapeless
Serialize whole classtrees.
object MyFormats extends DefaultJsonProtocol with FamilyFormats {
// gives a slight performance boost, but isn't necessary
implicit val MyAdtFormats = shapeless.cachedImplicit[JsonFormat[TestT]]
}
import MyFormats._

Related

Custom spray-json RootJsonFormat or JsonFormat depending on value in JSON object

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

How to parse a trait and objects to JSON in Scala Play Framework

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

Implicit encoder for a trait type parameter

I would like to encode to json a field of type List[E] using argonaut lib.
sealed trait Msg[E] {
val contents: List[E]
def send(): Unit = {
val json = contents.asJson
println("Sending json: " + json.toString())
}
}
Then I have a StringMsg case class:
case class StringMsg(contents: List[String]) extends Msg[String]
The argonaut lib defines the JsonIdentity[J] trait:
trait JsonIdentity[J] {
val j: J
/**
* Encode to a JSON value using the given implicit encoder.
*/
def jencode(implicit e: EncodeJson[J]): Json =
e(j)
}
When I create a new instance of StringMsg and call the send() method, I have the following error:
StringMsg(List("a","b")).send()
could not find implicit value for parameter e:
argonaut.EncodeJson[List[E]]
Your API should require implicit argonaut.EncodeJson[List[E]] from client code:
sealed trait Msg[E] {
val contents: List[E]
implicit def encodeJson: argonaut.EncodeJson[List[E]] //to be implemented in subclass
def send(): Unit = {
val json = contents.asJson
println("Sending json: " + json.toString())
}
}
//or
abstract class Msg[E](implicit encodeJson: argonaut.EncodeJson[List[E]]) {
val contents: List[E]
def send(): Unit = {
val json = contents.asJson
println("Sending json: " + json.toString())
}
}
//or
sealed trait class Msg[E] {
val contents: List[E]
def send()(implicit encodeJson: argonaut.EncodeJson[List[E]]): Unit = {
val json = contents.asJson
println("Sending json: " + json.toString())
}
}
Somewhere in the client-code:
case class StringMsg(contents: List[String]) extends Msg[String] {
implicit val encodeJson = argonaut.StringEncodeJson
}
//or
import argonaut.StringEncodeJson //or even import argonaut._
case class StringMsg(contents: List[String]) extends Msg[String]() //implicit will be passed here
//or
import argonaut.StringEncodeJson //or even import argonaut._
case class StringMsg(contents: List[String]) extends Msg[String]
val a = StringMsg(Nil)
a.send() //implicit will be passed here

Getting a Play JSON JsValueWrapper for a class that extends a trait

I'm generating JSON for a speed where the units may vary. I have a SpeedUnit trait and classes that extend it (Knots, MetersPerSecond, MilesPerHour). The JSON Play documentation said "To convert your own models to JsValues, you must define implicit Writes converters and provide them in scope." I got that to work in most places but not when I had a class extending a trait. What am I doing wrong? Or is there an Enum variant I could or should have used instead?
// Type mismatch: found (String, SpeedUnit), required (String, Json.JsValueWrapper)
// at 4th line from bottom: "speedunit" -> unit
import play.api.libs.json._
trait SpeedUnit {
// I added this to SpeedUnit thinking it might help, but it didn't.
implicit val speedUnitWrites = new Writes[SpeedUnit] {
def writes(x: SpeedUnit) = Json.toJson("UnspecifiedSpeedUnit")
}
}
class Knots extends SpeedUnit {
implicit val knotsWrites = new Writes[Knots] {
def writes(x: Knots) = Json.toJson("KT")
}
}
class MetersPerSecond extends SpeedUnit {
implicit val metersPerSecondWrites = new Writes[MetersPerSecond] {
def writes(x: MetersPerSecond) = Json.toJson("MPS")
}
}
class MilesPerHour extends SpeedUnit {
implicit val milesPerHourWrites = new Writes[MilesPerHour] {
def writes(x: MilesPerHour) = Json.toJson("MPH")
}
}
// ...
class Speed(val value: Int, val unit: SpeedUnit) {
implicit val speedWrites = new Writes[Speed] {
def writes(x: Speed) = Json.obj(
"value" -> value,
"speedUnit" -> unit // THIS LINE DOES NOT TYPE-CHECK
)
}
}
Writes is an example of a type class, which means you need a single instance of a Writes[A] for a given A, not for every A instance. If you're coming from a Java background, think Comparator instead of Comparable.
import play.api.libs.json._
sealed trait SpeedUnit
case object Knots extends SpeedUnit
case object MetersPerSecond extends SpeedUnit
case object MilesPerHour extends SpeedUnit
object SpeedUnit {
implicit val speedUnitWrites: Writes[SpeedUnit] = new Writes[SpeedUnit] {
def writes(x: SpeedUnit) = Json.toJson(
x match {
case Knots => "KTS"
case MetersPerSecond => "MPS"
case MilesPerHour => "MPH"
}
)
}
}
case class Speed(value: Int, unit: SpeedUnit)
object Speed {
implicit val speedWrites: Writes[Speed] = new Writes[Speed] {
def writes(x: Speed) = Json.obj(
"value" -> x.value,
"speedUnit" -> x.unit
)
}
}
And then:
scala> Json.toJson(Speed(10, MilesPerHour))
res0: play.api.libs.json.JsValue = {"value":10,"speedUnit":"MPH"}
I've put the Writes instances in the companion objects for the two types, but they can go elsewhere (if you don't want to mix up serialization concerns in your model, for example).
You can also simplify (or at least concise-ify) this a lot with Play JSON's functional API:
sealed trait SpeedUnit
case object Knots extends SpeedUnit
case object MetersPerSecond extends SpeedUnit
case object MilesPerHour extends SpeedUnit
case class Speed(value: Int, unit: SpeedUnit)
import play.api.libs.json._
import play.api.libs.functional.syntax._
implicit val speedWrites: Writes[Speed] = (
(__ \ 'value).write[Int] and
(__ \ 'speedUnit).write[String].contramap[SpeedUnit] {
case Knots => "KTS"
case MetersPerSecond => "MPS"
case MilesPerHour => "MPH"
}
)(unlift(Speed.unapply))
Which approach you take (functional or explicit) is largely a matter of taste.

Spray json marshalling

I'm on a quest to create a JSON API where some of the models could be nicely generalized. I'm a newbie in Spray, so I started out a spike with an over simplified example.
However I can't figure out what is going on with the bellow code...
I have imported both
my custom implicits and
spray.httpx.SprayJsonSupport._
As I understand this is what I have to do in order to have an implicit in scope that can convert from JsonFormat to Marshaller.
Compiler error:
TestService.scala:15: could not find implicit value for parameter um: spray.httpx.unmarshalling.FromRequestUnmarshaller[my.company.Test[my.company.X]]
Code:
package my.company
import spray.routing.HttpService
import spray.json.{JsValue, JsObject, JsonFormat, DefaultJsonProtocol}
trait TestService extends HttpService {
import my.company.TestImplicits._
import spray.httpx.SprayJsonSupport._
val test =
path("test") {
post {
entity(as[Test[X]]) {
test => {
complete(s"type: ${test.common}")
}
}
}
}
}
trait Common {
def commonData: String
}
case class X(id: Long, commonData: String) extends Common
case class Y(commonData: String) extends Common
case class Test[T <: Common](comment: String, common: T)
object TestImplicits extends DefaultJsonProtocol {
implicit val xFormat = jsonFormat2(X)
implicit val yFormat = jsonFormat1(Y)
implicit val yTestFormat: JsonFormat[Test[Y]] = new JsonFormat[Test[Y]] {
def write(test: Test[Y]) = JsObject()
def read(js: JsValue) = Test("test", Y("y"))
}
implicit val xTestFormat: JsonFormat[Test[X]] = new JsonFormat[Test[X]] {
def write(test: Test[X]) = JsObject()
def read(js: JsValue) = Test("test", X(1L, "y"))
}
}
I would appreciate any help. Thanks in advance.
SOLVED
Solution was (as #jrudolp suggested) both to:
Move implicit definitions on top of the file (surprising)
Create RootJsonFormat rather than JsonFormat.