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.
Related
I am writing tests for my case classes and in the following test I have a StackOverflowError:
test("ValuationRequest - Conversion between case object and Json works") {
val caseObject = ValuationRequest(TIME_SERIES_INTRADAY, "", IntraDayIntervals.MIN_5)
val jsonString = caseObject
.asJson
.printWith(Printer.noSpaces)
decode[ValuationRequest](jsonString) must be(Right(caseObject))
}
here's the stacktrace:
An exception or error caused a run to abort.
java.lang.StackOverflowError
at io.circe.syntax.package$EncoderOps$.asJson$extension(package.scala:10)
at eventbus.ValuationRequest$.$anonfun$encodeRequest$1(ValuationRequestCases.scala:14)
....
This is the case code:
import AV_Enums.TimeSeriesFunctions.TIME_SERIES_INTRADAY
import AV_Enums.{IntraDayInterval, IntraDayIntervals, TimeSeriesType}
import cats.syntax.functor._
import io.circe.generic.auto._
import io.circe.generic.semiauto._
import io.circe.syntax._
import io.circe.{Decoder, Encoder}
case class ValuationRequest(function: TimeSeriesType = TIME_SERIES_INTRADAY, symbol: String, interval: IntraDayInterval = IntraDayIntervals.MIN_5)
object ValuationRequest {
implicit val encodeRequest: Encoder[ValuationRequest] = Encoder.instance { case response#ValuationRequest(_, _, _) => response.asJson }
implicit val decodeRequest: Decoder[ValuationRequest] = deriveDecoder[ValuationRequest].widen
}
These are the enums it uses:
sealed abstract class TimeSeriesType(val text: String) extends EnumEntry {}
sealed abstract class IntraDayInterval(val text: String) extends EnumEntry {}
object TimeSeriesFunctions extends Enum[TimeSeriesType] with CirceEnum[TimeSeriesType] {
val values: immutable.IndexedSeq[TimeSeriesType] = findValues
case object TIME_SERIES_INTRADAY extends TimeSeriesType("TIME_SERIES_INTRADAY")
case object TIME_SERIES_DAILY extends TimeSeriesType("TIME_SERIES_DAILY")
case object TIME_SERIES_WEEKLY extends TimeSeriesType("TIME_SERIES_WEEKLY")
case object TIME_SERIES_MONTHLY extends TimeSeriesType("TIME_SERIES_MONTHLY")
}
object IntraDayIntervals extends Enum[IntraDayInterval] with CirceEnum[IntraDayInterval] {
val values: immutable.IndexedSeq[IntraDayInterval] = findValues
case object MIN_1 extends IntraDayInterval("1min")
case object MIN_5 extends IntraDayInterval("5min")
case object MIN_15 extends IntraDayInterval("15min")
case object MIN_30 extends IntraDayInterval("30min")
case object MIN_60 extends IntraDayInterval("60min")
}
I don't understand what is going on with this case, all the others work fine and are implemented the same way. Can anyone help?
Problematic line
Encoder.instance { case response#ValuationRequest(_, _, _) => response.asJson }
.asJson here requires Encoder[ValuationRequest] which is a recursive call. Any reason you cannot use deriveEncoder[ValuationRequest]?
I would like to build a simple stub server with several routes using Http AKKA.
I have the following case class:
case class Person(name: String, age: Int)
object Person {
implicit def cardJsonFormat: RootJsonFormat[Person] = jsonFormat2(Person.apply)
}
How can I return this case class as a JSON response.
My route looks like:
case class Person(name: String, age: Int)
def route =
get {
path("person") {
complete {
}
}
}
You should try something like this:
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import akka.http.scaladsl.server.Directives._
import spray.json.DefaultJsonProtocol
case class Person(name: String, age: Int)
case object Person extends SprayJsonSupport with DefaultJsonProtocol {
implicit val personFormat = jsonFormat2(Person.apply)
}
object PersonRoute {
val route =
get {
path("person") {
complete {
Person("Pawel", 25)
}
}
}
}
More details and examples can be found in the docs.
I found the following library akka-http-json used with json4s useful. It helps me cut down on the number of jsonFormatX statements. Every jsonFormatX statement is needed for every data type that needs to be marshalled / unmarshalled.
Just mix in the following trait where the marshall / unmarshall needs to occur:
import de.heikoseeberger.akkahttpjson4s.Json4sSupport
import org.json4s.jackson
trait JsonCodec extends Json4sSupport {
import org.json4s.DefaultFormats
import org.json4s.ext.JodaTimeSerializers
implicit val serialization = jackson.Serialization
implicit val formats = DefaultFormats ++ JodaTimeSerializers.all
}
I'm just starting out with Akka HTTP and I'm having a bit of trouble with the routing DSL and marshaling. The tilde in the 'route' setup results in the error:
value ~ is not a member of akka.http.scaladsl.server.RequestContext ⇒
scala.concurrent.Future[akka.http.scaladsl.server.RouteResult]
possible cause: maybe a semicolon is missing before 'value ~'?
Also, the JSON marshaling in the 'get' clause causing the error:
◾Cannot find JsonWriter or JsonFormat type class for
scala.collection.immutable.Map[String,scala.collection.mutable.Map[String,Tweet]]
◾not enough arguments for method toJson: (implicit writer:
spray.json.JsonWriter[scala.collection.immutable.Map[String,scala.collection> .mutable.Map[String,Tweet]]])spray.json.JsValue.
Unspecified value parameter writer.
I've followed the documentation examples fairly closely, so I'd appreciate help in understanding these errors and how to resolve them. Thanks.
API
import akka.actor.ActorSystem
import scala.concurrent.Future
import akka.stream.ActorMaterializer
import akka.http.scaladsl.Http
import akka.http.scaladsl.server.Directives.path
import akka.http.scaladsl.server.Directives.pathPrefix
import akka.http.scaladsl.server.Directives.post
import akka.http.scaladsl.server.Directives.get
import akka.http.scaladsl.server.Directives.complete
import akka.http.scaladsl.unmarshalling.Unmarshal
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import akka.http.scaladsl.server.Directives.{entity, as}
import akka.http.scaladsl.model.StatusCodes.{Created, OK}
import spray.json._
import DefaultJsonProtocol._
import akka.stream.Materializer
import scala.concurrent.ExecutionContext
trait RestApi {
import TweetProtocol._
import TweetDb._
implicit val system: ActorSystem
implicit val materializer: Materializer
implicit val execCtx: ExecutionContext
val route =
pathPrefix("tweets") {
(post & entity(as[Tweet])) { tweet =>
complete {
Created -> Map("id" -> TweetDb.save(tweet)).toJson
}
} ~
(get) {
complete {
OK -> Map("tweets" -> TweetDb.find()).toJson
}
}
}
}
object TweetApi extends App with RestApi {
implicit val system = ActorSystem("webapi")
implicit val materializer = ActorMaterializer()
implicit val execCtx = system.dispatcher
val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
Console.readLine()
bindingFuture.flatMap(_.unbind()).onComplete { _ => system.shutdown() }
}
Protocol
import spray.json.DefaultJsonProtocol
case class Tweet(author: String, body: String)
object TweetProtocol extends DefaultJsonProtocol {
implicit val TweetFormat = jsonFormat2(Tweet.apply)
}
Pseudo-database
import scala.collection.mutable.Map
object TweetDb {
private var tweets = Map[String, Tweet]()
def save(tweet: Tweet) = {
val id: String = java.util.UUID.randomUUID().toString()
tweets += (id -> tweet)
id
}
def find() = tweets
def findById(id: String) = tweets.get(id)
}
For your 1st error, try the suggestion from the comment, ie. import all from Directives
For second part
◾Cannot find JsonWriter or JsonFormat type class for scala.collection.immutable.Map[String,scala.collection.mutable.Map[String,Tweet]]
◾not enough arguments for method toJson: (implicit writer: spray.json.JsonWriter[scala.collection.immutable.Map[String,scala.collection> .mutable.Map[String,Tweet]]])spray.json.JsValue. Unspecified value parameter writer.
You need to define JsonFormat for Map[String, mutable.Map[String, Tweet]]
By creating an object in your TweetProtocol, extending RootJsonFormat
eg.
type DBEntry = Map[String, mutable.Map[String, Tweet]]
object TweetProtocol extends DefaultJsonProtocol {
implicit object DBEntryJsonFormat extends RootJsonFormat[DBEntry] {
override def read(json: JSValue) {
// your implementation
}
override def write(dbEntry: DBEntry) {
// implementation
}
}
}
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)
}
}
}
I have a recursive data structure that I want to write a custom spray-json serializer for.
case class Counts(var count: Int, var properties: mutable.Map[String, Counts])
object MyJsonProtocol extends DefaultJsonProtocol {
import DefaultJsonProtocol._
implicit object CountsJsonFormat extends RootJsonFormat[Counts] {
def read(json: JsValue) = ???
def write(c: Counts) = {
// Flatten count and properties into the same object.
val properties = c.properties.toJson.asJsObject
val fields = properties.fields + ("count" -> JsNumber(c.count))
JsObject(fields.toSeq: _*)
}
}
}
I've seen the documentation for how to do this for a case class if you use the builtin serialization logic, but I have no idea how to apply that to a custom serializer. I get this compiler error:
Cannot find JsonWriter or JsonFormat type class for scala.collection.mutable.Map[String,co.asku.acuity.EventCounter.Counts]
val properties = c.properties.toJson.asJsObject
^
spray-json formats can't handle mutable Maps by default (see this disussion that has happened a while ago on the mailing list). Change the type of properties to be an immutable Map (which I think is better anyways) and your format will work as expected.
To add to edi's answer, using toMap would have worked in the example I posted, to convert the mutable map to immutable.
However, I actually ran into a more complex usecase using nested mutable maps, so I just added a format to serialize them like this:
object JsonProtocol extends DefaultJsonProtocol {
import DefaultJsonProtocol._
implicit def mutableMapFormat[K :JsonFormat, V :JsonFormat] = new RootJsonFormat[mutable.Map[K, V]] {
def read(value: JsValue) = ???
def write(m: mutable.Map[K, V]) = m.toMap.toJson
}
implicit object CountsJsonFormat extends RootJsonFormat[Counts] {
// ...
}
}
This code provides serialization and deserialization support for mutable maps (and can be modified trivially for other mutable collections):
import spray.json._
import spray.json.DefaultJsonProtocol._
import scala.collection.mutable
...
implicit def mutableMapFormat[K : JsonFormat, V : JsonFormat] = new RootJsonFormat[mutable.Map[K, V]] {
def write(m : mutable.Map[K, V]) = mapFormat[K, V].write(m.toMap)
def read(value : JsValue) = mutable.Map.empty[K, V] ++ mapFormat[K, V].read(value)
}