Scalatra Code:
import org.scalatra._
import org.json4s.{DefaultFormats, Formats}
import org.scalatra.json._
class AppServlet extends AppStack with JacksonJsonSupport{
protected implicit lazy val jsonFormats: Formats = DefaultFormats
private def generateJSON():((String, String),(String, String)) = {
val json = ("Firstname" -> "joe", "LastName" -> "cole")
json
}
before() {
contentType = formats("json")
}
get("/") {
generateJSON
}
}
I am trying to return simple json using scalatra framework and the output is something like this {"_1":{"Firstname":"joe"},"_2":{"LastName":"cole"}}. I dont need _1 or _2 to be printed. Please note i am not trying to return any objects. I just need to make my own json and then return it. It is not associated with any data model. Any help is appreciated.
What you create is a tuple of (String, String), that's not surprising the output is like that. You should either create a case class, or, since you use json4s, return:
// don't forget this:
// import org.json4s.JsonDSL._
("Firstname" -> "joe") ~ ("LastName" -> "cole")
import org.scalatra._
import org.json4s.{DefaultFormats, Formats}
import org.scalatra.json._
import org.json4s._
import org.json4s.JsonDSL._
class AppServlet extends AppStack with JacksonJsonSupport{
protected implicit lazy val jsonFormats: Formats = DefaultFormats
private def generateJSON():JObject = {
val json = ("Firstname" -> "joe") ~ ("LastName" -> "cole")
json
}
get("/") {
generateJSON
}
}
Related
I spent a whole day figuring out how to
Consume a remote restful json service
Unmarshall (deserialize) the payload into a case class model
The guys over at the google plus Akka mailing list were very helpful, so I thought it would be nice to have a working example on SO as well, for future reference.
Akka-Http 10.0.5
package xxx
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import akka.http.scaladsl.model._
import akka.http.scaladsl.unmarshalling.Unmarshal
import akka.stream.{ActorMaterializer, Materializer}
import org.scalatest.concurrent.ScalaFutures
import org.scalatest.{BeforeAndAfter, FlatSpec, MustMatchers}
import org.scalatest.mock.MockitoSugar
import spray.json.DefaultJsonProtocol
import scala.concurrent.{Await, ExecutionContextExecutor, Future}
import akka.http.scaladsl.server.Directives
case class ColorBlob(url: String, averageColor: String, classificationColor: String)
case class ColorBlobsResponse(colorBlobs: Map[String, Option[ColorBlob]])
trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol {
implicit val format1 = jsonFormat3(ColorBlob)
implicit val format2 = jsonFormat1(ColorBlobsResponse)
}
class ColorBlobRestTest
extends FlatSpec
with MockitoSugar
with BeforeAndAfter
with MustMatchers
with ScalaFutures
with JsonSupport
with Directives {
implicit val system: ActorSystem = ActorSystem()
implicit val executor: ExecutionContextExecutor = system.dispatcher
implicit val materializer: Materializer = ActorMaterializer()
"this" should "work" in {
val request = HttpRequest(method = HttpMethods.GET, uri = s"https://colorservice/colorblobs/en?productCodes=904655")
val futureResponse: Future[HttpResponse] = Http().singleRequest(request)
val futureColorBlobResponse: Future[ColorBlobsResponse] = futureResponse.flatMap { response: HttpResponse =>
val entity: ResponseEntity = response.entity
Unmarshal(entity).to[ColorBlobsResponse]
}
import scala.concurrent.duration._
val colorBlobsResponse: ColorBlobsResponse = Await.result(futureColorBlobResponse, 1000.millis)
assert(1==1)
}
}
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
}
}
}
I am trying to exclude the _passthroughFields property in the example below. When I use the debugger, it looks like my PropertyFilter is never used. What am I doing wrong?
import java.util
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter.SerializeExceptFilter
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider
import com.fasterxml.jackson.databind.{DeserializationFeature, ObjectMapper, ObjectWriter}
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
import org.scalatest.{Matchers, WordSpec}
import scala.collection.immutable.Map
class PassthroughFieldsSpec extends WordSpec with Matchers {
"JacksonParser" when {
"given an Object and undesired fields" should {
"not include those fields in the json response" in {
trait Foo {
def id: String
def _passthroughFields: Map[String, String] = Map.empty
}
class Bar(val id: String, override val _passthroughFields: Map[String, String]) extends Foo
val item = new Bar("abcd", Map.empty)
val mapper = new ObjectMapper() with ScalaObjectMapper
mapper.registerModule(DefaultScalaModule)
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
val excludes = new util.HashSet[String](1)
excludes.add("_passthroughFields")
excludes.add("get_passthroughFields")
val filters = new SimpleFilterProvider()
.addFilter("filter properties by name", new SerializeExceptFilter(excludes))
val writer: ObjectWriter = mapper.writer(filters)
val json = writer.writeValueAsString(item)
json.contains("_passthroughFields") shouldBe false
}
}
}
}
I think you can exclude it using something like #JsonIgnore or make the field transient.
Otherwise if you need to define it outside of the case class code (like in your example) you can do it with Genson.
import com.owlike.genson._
import com.owlike.genson.reflect.VisibilityFilter
// Note that if you use case classes you don't need the visibility filter stuff
// it is used to indicate that private fields should be ser/de
val genson = new GensonBuilder()
.withBundle(ScalaBundle())
.useFields(true, VisibilityFilter.PRIVATE)
.exclude("_passthroughFields")
.create()
genson.toJson(item)
Disclaimer: I am Gensons author.
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)
}
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.