Having trouble getting Writes to work with Scala Play - json

To begin with I would like to say sorry for long post, and I really appreciate those who still look into my problem.
I have a controller that should return a json-response with a structure like:
{
result: [
{
key: value,
key: value,
key: value,
key: [
{
key: value,
key: value,
key: value
},...
]
},....
]
}
However I have problems getting the Writes to work as I want.
Note. I will add comments under the line where I have problem.
object APIController extends Controller {
def feed() = Action {
val objects = repo.getObjects().toList
Ok(Json.toJson(Json.obj("result" -> Class_1.apply(objects).result)))
}
first off, if I don't make a Json.obj("result" -> List[objects]) the result key isn't shown in the JSON-result. If I add a Writer for that I get errors saying that the List[objects] must have a Writer. But if I write it like above it doesn't need a Writer for the List[objects]
case class Class_1 (result: Seq[Class_2])
object Class_1 {
def apply(objs: List[Object]) = {
var result:ListBuffer[Class_2] = ListBuffer[Class_2]()
for(obj <- objs) feedResult += Class_2.apply(code)
new Class_1(result.toList)
}
}
*this is where I would put the Writer for Class_1. But if I do this like
implicit val class1Writer = new Writes[Class_1] {
override def writes(o: Class_1): JsValue = Json.obj("result" -> o.result)
} I get the problems I mentioned earlier, that I suddenly need a Writes for a List[objects] of that type*
case class Class_2 (id: Long, id2: Long, listOfStuff: Seq[Class_3])
object Class_2 {
def apply(obj: Object) = {
var items: ListBuffer[Class_3] = ListBuffer[Class_3]()
for(obj1 <- obj.getListOfStuff()) items += Class_3.apply(obj1)
new Class_2(obj.firstID, obj.secID, items.toList)
}
}
implicit val class2Writes = new Writes[Class_2] {
override def writes(o: Class_2): JsValue = {
Json.obj(
"id" -> o.id,
"id2" -> o.id2,
"items" -> o.listOfStuff
)
}
}
*the "items" -> o.listOfStuff says it needs a Writes for a List[types in the list] but I have a Writes for the objects in the list (Class_3) and I don't need a Writes for when serializing a list of objects from Class_2, why is it behaving like this?*
case class Class_3 (id: Long, text: String)
object Class_3 {
def apply(obj: Object) = {
new Class_3(obj.id, obj.funnyText)
}
}
implicit val class3Writer = new Writes[Class_3] {
override def writes(o: Class_3): JsValue = {
Json.obj(
"id" -> o.id,
"text" -> o.text
)
}
}
}
The error I get from this code is:
No Json deserializer found for type Seq[Class_3]. Try to implement an implicit Writes or Format for this type.
[error] "items" -> o.listOfStuff
[error] ^
If I remove this line in the Writes it compiles and works.
And I think that's weird since the first list I serialize doesn't have a Writer, only for the objects in the list.
Does anyone know why it behaves like this?
What should I do to accomplish what I'm after? (I hope you see what I'm trying to do)
Thanks in advance.

Just put the implicit val class3Writer ahead of class2Writes

Related

How to return json from Play Scala controller?

I would like to know that how can I return json response data from Play(2.2.x) Scala controller class to display on my view page ? I have json objects in Postgresql database(table name: "test" and having: id and name). Please provide me any solutions for it.
I have tried the following cases(a and b), but I am not sure why I am not getting the response(like: names) on my controller, so I can show them on my view page ? since I am very new to Play/Scala and Postgresql.
case a. If I give like:
model:
def getTestValuesFromTable() = {
DB.withConnection { implicit connection =>
val selectJson =SQL("select name from test").on().apply().collect {
case Row(id:Long, Some(name:String)) =>
new TestContent(name)
}
//.head
//JsObject(selectJson().map { row => row[Long]("id").toString -> JsString(row[String]("name")) }.toSeq)
}
}
controller:
def getTest = Action {
val response = TestContent.getTestValuesFromTable()
Ok("Done")
//Ok(response)
}
Output is: Done(application is executing fine without any exceptions, of course json data is not coming since I am returning: Done only, so getting output: "Done")
case b. If I do like this: getting error: not enough arguments for method apply: (n: Int)models.Testname in trait LinearSeqOptimized. Unspecified value parameter n. I really not sure how can I get my response for it ?
controller:
def getTest = Action {
val response = TestContent.getTestValuesFromTable()
// Ok("Done")
Ok(response)
}
model:
def getTestValuesFromTable(): JsValue = {
DB.withConnection { implicit connection =>
val selectJson = SQL("select * from test")
JsObject(selectJson().map { row => row[Long]("id").toString -> JsString(row[String]("name")) }.toSeq)
// val selectJson =SQL("select name from test").on().apply().collect {
// case Row(id:Long, Some(name:String)) =>
// new TestContent(name)
// }
//.head
JsObject(selectJson().map { row => row[Long]("id").toString -> JsString(row[String]("name")) }.toSeq)//not enough arguments for method apply: (n: Int)models.Testname in trait LinearSeqOptimized. Unspecified value parameter n.
}
}
Please let me know how to get my response ?
getJsonValuesFromTable method return nothing (Unit). To fix it change definition of this method to
def getJsonValuesFromTable(testContent: TestContent) = {
or explicitly setting type:
def getJsonValuesFromTable(testContent: TestContent): Unit = {
Also as a next step to let client know that you are returning json, you should set content type:
Ok(Json.obj(response)).as("application/json")

Scala, Akka, Spray: How to validate json data before processing?

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.

json4s object extraction with extra data

I'm using spray with json4s, and I've got the implementation below to handle put requests for updating objects... My problem with it, is that I first extract an instance of SomeObject from the json, but being a RESTful api, I want the ID to be specified in the URL. So then I must somehow create another instance of SomeObject that is indexed with the ID... to do this, I'm using a constructor like SomeObject(id: Long, obj: SomeObject). It works well enough, but the implementation is ugly and it feels inefficient. What can I do so I can somehow stick the ID in there so that I'm only creating one instance of SomeObject?
class ApplicationRouter extends BaseRouter {
val routes =
pathPrefix("some-path") {
path("destination-resource" \ IntNumber) { id =>
entity(as[JObject]) { rawData =>
val extractedObject = rawData.camelizeKeys.extract[SomeObject]
val extractedObjectWithId = SomeObject(id, extractedObject)
handleRequest(extractedObjectWithId)
}
}
}
}
case class SomeObject(id: Long, data: String, someValue: Double, someDate: DateTime) {
def this(data: String, someValue: Double, someDate: DateTime) = this(0, data, someValue, someDate)
def this(id: Long, obj: SomeObject) = this(id, obj.data, obj.someValue, obj.someDate)
}
I figured out a solution to this after digging around through the docs for awhile:
class ApplicationRouter extends BaseRouter {
val routes =
pathPrefix("some-path") {
path("destination-resource" \ IntNumber) { id =>
entity(as[JObject]) { rawData =>
val extractedObject = rawData.camelizeKeys.merge {
("id", id)
}.extract[SomeObject]
handleRequest(extractedObject)
}
}
}
}
Since id field is not set on all instances, it means it is optional so use Option type to indicate it. Define your case class with id: Option[Long] field. This make json parser to skip id field when it is absent but allow you to assign a value when you have.
case class SomeObject(id: Option[Long], data: String, someValue: Double, someDate: DateTime)
class ApplicationRouter extends BaseRouter {
val routes =
pathPrefix("some-path") {
path("destination-resource" \ IntNumber) { id =>
entity(as[JObject]) { rawData =>
val extractedObject = rawData.camelizeKeys.extract[SomeObject]
val extractedObjectWithId = extractedObject.copy(id = Some(id))
handleRequest(extractedObjectWithId)
}
}
}
}
And don't worry about performance impacts of creating new objects. It may affect performance much less than you thought. You should measure performance before improving it.
I do not know about efficiency, but you can make your code "less ugly" by defining a SomeObjectBuilder, to which you extract your JSON value.
case class SomeObjectBuilder(data: String, someValue: Double, someDate: DateTime) {
def setId(id: Long) = SomeObject(id, data, someValue, someDate)
}
case class SomeObject(id: Long, data: String, someValue: Double, someDate: DateTime)
With the extraction:
class ApplicationRouter extends BaseRouter {
val routes =
pathPrefix("some-path") {
path("destination-resource" \ IntNumber) { id =>
entity(as[JObject]) { rawData =>
val extractedObject = rawData.camelizeKeys.extract[SomeObjectBuilder]
val extractedObjectWithId = extractedObject.setId(id)
handleRequest(extractedObjectWithId)
}
}
}
}
This way, you are not using a default id set to zero, which is, if I understand correctly, never correct. The only reason you're setting it to zero is that the value is not known by the extractor, so, using the builder, you make a partial instantiation explicit.

Render json data to the view with play! scala 2.2.3

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

grails.converters.JSON except few properties

I am using grails-1.3.2 and hbase-0.2.4.
I have the following domain class:
class MyClass{
String val1
String val2
String val3
//----
}
class MyClassController{
def someAction = {
def myClass = new MyClass()
//----
String valAsJson = (myClass as JSON)
render valAsJson
}
}
My question is, is any short way render only part of properties(for example render all except val3 property) ?
You can do something like this :
def myClass = MyClass.get(1)
//include
render myClass.part(include:['val1', 'val2']) as JSON
//except
render job.part(except:['val2','val3']) as JSON
Bootstrap.groovy :
import org.codehaus.groovy.grails.orm.hibernate.support.ClosureEventTriggeringInterceptor as Events
class BootStrap {
def grailsApplication
def excludedProps = [Events.ONLOAD_EVENT,
Events.BEFORE_DELETE_EVENT, Events.AFTER_DELETE_EVENT,
Events.BEFORE_INSERT_EVENT, Events.AFTER_INSERT_EVENT,
Events.BEFORE_UPDATE_EVENT, Events.AFTER_UPDATE_EVENT]
def init = { servletContext ->
grailsApplication.domainClasses.each{ domainClass ->
domainClass.metaClass.part= { m ->
def map= [:]
if(m.'include'){
m.'include'.each{
map[it]= delegate."${it}"
}
}else if(m.'except'){
m.'except'.addAll excludedProps
def props= domainClass.persistentProperties.findAll {
!(it.name in m.'except')
}
props.each{
map[it.name]= delegate."${it.name}"
}
}
return map
}
}
}
def destroy = {
}
}
If you know how to create our own plugin, then just create one plugin for this, so that you can use it across all the grails applications.
If you want to only include specific properties all the time, you would really want to use the ObjectMarshaller interface. See this article for more details.
If you simply want to render an instance of MyClass as JSON, excluding certain properties, here's a solution that uses the JSONBuilder class provided by Grails
import grails.web.JSONBuilder
class MyClassController{
def someAction = {
def myClass = new MyClass()
def builder = new JSONBuilder.build {
myClass.properties.each {propName, propValue ->
// Properties excluded from the JSON
def excludes = ['class', 'metaClass', 'val3']
if (!excludes.contains(propName)) {
setProperty(propName, propValue)
}
}
render(text: builder.toString(), contentType: 'application/json')
}
}
Or, you could just create a map of the properties you wanted, then encode them as JSON
Map m = [ 'val1', 'val2' ].inject( [:] ) { map, val -> map."$val" = a."$val" ; map }
render m as JSON
To exclude properties, you would need to do something like this (UNTESTED)
def exclude = [ 'val3' ]
Map m = new DefaultGrailsDomainClass( MyClass.class ).properties.findAll {
!( it.name in exclude )
}.inject( [:] ) { map, val ->
map."$val.name" = a."$val.name" ; map
}
render m as JSON
The JSON Exclusion Marshaller Plugin
I needed to solve this problem recently. I went ahead and packaged the solution into a plugin that allows you to easily exclude class properties from the JSON converter's output. It is available on the Grails Plugin Portal.
After you install the plugin, you will have access to a method on the grails.converters.JSON class called excludeFor*().
More extensive documentation can be found here: How to use the JSON Exclusion Marshaller
But basically it can be used as such:
import grails.converters.JSON
def json, resultTeachersWillSee, resultOtherStudentsWillSee
// Given a TestStudent Domain Class
def student = new TestStudent([
firstName: "Tobias",
lastName: "Funke",
gradePointAverage: 3.6,
studentID: "FS-210-7312",
socialSecurityNumber: "555-55-5555"
])
student.save(flush: true)
// When
JSON.excludeForTeachers(TestStudent, ['socialSecurityNumber', 'id', 'class'])
JSON.use('excludeForTeachers') {
json = new JSON(student)
}
resultTeachersWillSee = json.toString()
// Then
assert resultTeachersWillSee == '{"firstName":"Tobias",
"gradePointAverage":3.6, "lastName":"Funke",
"studentID":"FS-210-7312"}'
// And When
JSON.excludeForOtherStudents(TestStudent, ['gradePointAverage', 'studentID',
'socialSecurityNumber', 'id', 'class'])
JSON.use('excludeForOtherStudents') {
json = new JSON(student)
}
resultOtherStudentsWillSee = json.toString()
// Then
assert resultOtherStudentsWillSee == '{"firstName":"Tobias",
"lastName":"Funke"}'
JSON.excludeForTeachers(...) creates a named object marshaller called "excludeForTeachers". The marshaller excludes three properties of the student object from the resulting JSON output. the 'socialSecurityNumber' property is explicitly defined in the class, while the 'id' property was added by GORM behind the scenes. In any case, teachers don't need to see any of those properties.
The plugin is serving me well... I hope others find it helpful too.