I'm using spray and I need to return a json object through a method.
val route =
path("all-modules") {
get {
respondWithMediaType(`text/html`) {
complete( configViewer.findAllModules.toString)
}
}
}
This prints ConfigResults(S1000,Success,List(testDataTypes, mandate, sdp))
But I need get this as the json object. how can I do it?
I tried in this way
val route =
path("all-modules") {
get {
respondWithMediaType(`application/json`) {
complete{
configViewer.findAllModules
}
}
}
}
It gives an compilation error could not find implicit value for parameter marshaller: spray.httpx.marshalling.ToResponseMarshaller
You need to tell Spray how it should serialize your case class.
Just configure something like
object JsonSupport {
implicit val formatConfigResults = jsonFormat3(ConfigResults)
}
The number in jsonFormat'number' stands for the number of members in your case class.
Then you just need to import into your route, the class where you define this implicit.
import JsonSupport._
Related
I am trying to serialize a kotlin.collections.Map from Json using Ktor and I continue to get an error, basically telling me it doesn't know how. I assumed this case was basic.
val beans = beans {
bean("oauthClient") {
HttpClient(CIO) {
expectSuccess = true
install(DefaultRequest){
url("${env["oauth.url"]!!}/ms_oauth/oauth2/endpoints/oauthservice/tokens")
}
install(Auth) {
basic {
credentials {
BasicAuthCredentials(username = env["client.id"]!!, password = env["client.secret"]!!)
}
}
}
install(ContentNegotiation) {
json()
}
}
}
bean {
val oauthResults: Map<String,Any> = runBlocking { // blows up here
ref<HttpClient>("oauthClient").submitForm("${env["oauth.url"]!!}/ms_oauth/oauth2/endpoints/oauthservice/tokens",
Parameters.build {
append("grant_type", "password")
append("scope", "bug.rest.public")
append("username", env["ssoUsername"]!!)
append("password", env["ssoPassword"]!!)
}
).body()
}
}
}
error:
Caused by: io.ktor.client.call.NoTransformationFoundException:
No transformation found: class io.ktor.utils.io.ByteBufferChannel -> class kotlin.collections.Map
Do I have to explicitly enable something? Or am I making a different mistake?
Update
I've since tried to marshal it into a data object and I get the same error, but referencing that object:
#Serializable
data class Oauth constructor(
#SerialName("expires_in")
val expiration: Int,
#SerialName("token_type")
val tokenType:String,
#SerialName("access_token")
val accessToken: String
)
error:
io.ktor.client.call.NoTransformationFoundException:
No transformation found: class io.ktor.utils.io.ByteBufferChannel -> class com.company.Oauth
I can process this json when all the inputs are valid, i.e with valid keys (including case) and values. The next step is to validate keys and return 400 (Bad Request) if the keys or values are invalid. What is a good way to add this validation?
The API call
POST http://localhost:8080/api/v1/adsession
Content-Type: application/json
body {
"sessionId": "abcd123123123",
"serviceGroup": "1234",
"targetCode": {"zipcodes":"30096,30188","code2":"value2"}
}
Route handler
class AdSessionRoutes(services: Services)(implicit ec: ExecutionContext, log: LoggingContext) extends ApiRoute(services) {
implicit val timeout = Timeout(10 seconds)
val postSession = pathPrefix("adsession") & pathEnd & post
val route: Route = {
withService("adSession") { service =>
postSession {
entity(as[AdSession]) { adSession =>
log.info(s"Processing POST ${adSession}")
val future = (service ? CreateAdSession(adSession)).mapTo[AdSession]
onComplete(future) {
case Success(result) =>
complete(StatusCodes.Created, result)
case Failure(e) =>
log.error(s"Error: ${e.toString}")
complete(StatusCodes.InternalServerError, Message(ApiMessages.UnknownException))
}
}
}
}
}
}
Model object
case class AdSession(
sessionId: String,
serviceGroup: String,
targetCodes: Map[String,String],
id: Option[String] = None)
object AdSessionJsonProtocol extends DefaultJsonProtocol {
implicit val adSessionFormat = jsonFormat4(AdSession)
}
entity(as[AdSession]) does map keys to case class fields, but I'm not sure how to catch those errors. I would like to capture those errors as well as add additional validations and return 400 with valid json error response.
I know this may be a little late but since akka-http-2.4.6, you can put the verification logic inside a case class. Check out this for info on how to do it.
Define your own read and write methods for AdSession like this:
object AdSessionJsonProtocol {
implicit object adSessionJsonFormat extends RootJsonFormat[AdSession] {
override def read(json: JsValue): AdSession = ???
override def write(obj: AdSession): JsValue = ???
}
}
Inside read function you can perform additional validation.
Another question is how to pass collected errors to Spray route. I would like to suggest you to wrap AdSession into Either[String, AdSession], for example:
postSession {
entity(as[Either[String, AdSession]]) { adSession =>
So, now your adSessionJsonFormat will looks like:
implicit object adSessionJsonFormat extends RootJsonFormat[Either[String, AdSession]] {
override def read(json: JsValue): Either[String, AdSession] = {
// json.asJsObject.fields access fields, perform checks
// if everything is OK return Right(AdSession(...))
// otherwise return Lift("Error Message")
}
override def write(obj: Either[String, AdSession]): JsValue = ???
}
But, I think it's possible to solve it in more elegant way using some implicit magic.
Here is a beginner question :
I have defined Event like this :
case class Event(id: Pk[Long], name: String, userId: Pk[Long])
object Event {
private val EventParser: RowParser[Event] = {
get[Pk[Long]]("id") ~
get[String]("name") ~
get[Pk[Long]]("userId") map {
case id ~ name ~ userId => Event(id, name, userId)
}
}
def findAll(): Seq[Event] = {
DB.withConnection { implicit connection =>
SQL("select * from events").as(EventParser *)
}
}
}
And I render it to the view like this :
def events = Action {
val events: Seq[Event] = Event.findAll()
Ok(views.html.events(events))
}
But I would like to return Json data.
Json.toJson(events) can't be used since events type is Seq.
I didn't find a good tutorial on the subject and I tried to follow this answer : play framework working with json objects in Scala but it doesn't seem to work with play 2.2.
So my question is : Do you know an easy way to render a sequence in Json to the view after accessing database?
Try this:
import play.api.libs.json._
object Event {
...
implicit def pkWrites[T : Writes]: Writes[Pk[T]] = Writes {
case anorm.Id(t) => implicitly[Writes[T]].writes(t)
case anorm.NotAssigned => JsNull
}
implicit val eventWrites = Json.writes[Event]
}
I am trying to move some code from a grails service file into src/groovy for better reuse.
import grails.converters.JSON
import org.codehaus.groovy.grails.web.json.JSONObject
class JsonUtils {
// seems like a clunky way to accomplish converting a domainObject
// to its json api like object, but couldn't find anything better.
def jsonify(obj, ArrayList removeableKeys = []) {
def theJson = obj as JSON
def reParsedJson = JSON.parse(theJson.toString())
removeableKeys.each { reParsedJson.remove(it) }
return reParsedJson
}
// essentially just turns nested JSONObject.Null things into java null things
// which don't get choked on down the road so much.
def cleanJson(json) {
if (json instanceof List) {
json = json.collect(cleanJsonList)
} else if (json instanceof Map) {
json = json.collectEntries(cleanJsonMap)
}
return json
}
private def cleanJsonList = {
if (it instanceof List) {
it.collect(cleanJsonList)
} else if (it instanceof Map) {
it.collectEntries(cleanJsonMap)
} else {
(it.class == JSONObject.Null) ? null : it
}
}
private def cleanJsonMap = { key, value ->
if (value instanceof List) {
[key, value.collect(cleanJsonList)]
} else if (value instanceof Map) {
[key, value.collectEntries(cleanJsonMap)]
} else {
[key, (value.class == JSONObject.Null) ? null : value]
}
}
}
but when I try to call jsonify or cleanJson from services I get MissingMethodExceptions
example call from grails service file:
def animal = Animal.read(params.animal_id)
if (animal) json.animal = JsonUtils.jsonify(animal, ['tests','vaccinations','label'])
error:
No signature of method: static org.JsonUtils.jsonify() is applicable for argument types: (org.Animal, java.util.ArrayList) values: [ ...]]\ Possible solutions: jsonify(java.lang.Object, java.util.ArrayList), jsonify(java.lang.Object), notify()
Also tried making the jsonify take an animal jsonify(Animal obj, ...) then it just said Possible solutions: jsonify(org.Animal, ...
The cleanJson method was meant to deal with JSONObject.Null things which have caused problems for us before.
example call:
def safeJson = JsonUtils.cleanJson(json) // json is request.JSON from the controller
error:
groovy.lang.MissingMethodException: No signature of method: static org.JsonUtils.cleanJson() is applicable for argument types: (org.codehaus.groovy.grails.web.json.JSONObject) values: [[...]]
Possible solutions: cleanJson(org.codehaus.groovy.grails.web.json.JSONObject)
All this code worked as it is when it was in service file. I am running grails 2.3.11 BTW
You've declared jsonify() and cleanJson() as instance methods and try to use them as static. Declare them as static and it should work:
class JsonUtils {
def static jsonify(obj, ArrayList removeableKeys = []) {
(...)
}
def static cleanJson(json) {
(...)
}
}
You need to define jsonify() and cleanJson() as static in order to call them statically.
For example I have next Domain class
User{
String name
}
Also I have 2 objects of this class
new User(name: "John").save()
new User(name: "Alex").save()
How should look "list" action in UserController to represent User.list() in JSON format like this
{1: "John", 2: "Alex"}
Let me be more precise. I want something like this:
UserController{
def list = {
render(contentType: "text/json") {
User.list().each {user->
user.id = user.name
}
}
}
But sadly this isn't working.
Try the array structure,
def list = {
render(contentType: "text/json") {
results = array {
User.list().each {user->
result "${user.id}" : "${user.name}"
}
}
}
}
I couldn't find solution with JSONBuilder API. Because of that I made my solution with help of org.codehaus.jackson.
response.setContentType("text/json")
JsonGenerator g = jsonFactory.createJsonGenerator(response.getOutputStream())
g.writeStartObject()
for (user in users) {
g.writeStringField(user.id.toString(), user.name)
}
g.writeEndObject()
g.close()
When I want to encode something as JSON in grails, I put everything in maps:
render ['1':john.name, '2':alex.name] as JSON
Starting from #aldrin answer, a correction is needed for GRAILS3 json rendering because array directive is no more working (and is more correct 'application' instead of 'text'), so the solution must be
def list = {
def ulist = User.list()
render(contentType: "application/json") {
results(ulist) { user ->
userid user.id
username user.name
}
}
}