I don't understand how can i exclude some properties when rendering an object as JSON in a controller (not a RESTController but a classic Controller).
I have this resources.groovy:
// Place your Spring DSL code here
import grails.rest.render.json.JsonRenderer
import com.appromocodes.Promocode
import com.appromocodes.ResponseStatus
import grails.rest.render.json.JsonCollectionRenderer
beans = {
responseStatusRenderer(JsonRenderer, ResponseStatus) {
excludes = ['enumType']
}
promocodeRenderer(JsonRenderer, Promocode) {
excludes = ['class', 'id', 'project']
}
}
Within my controller i tried in my action something like:
respond p as JSON
but this still gives me all the fields (also class, id and project fields).
What should i do?
The proper way to handle this is to register a custom JSON marshaller for your object. Begin by creating a new Marshaller in src/groovy/packageName/marshallers/PromocodeMarshaller.groovy with the following contents:
import packageName.Promocode
import grails.converters.JSON
class PromocodeMarshaller {
void register() {
JSON.registerObjectMarshaller(Promocode) { promocode ->
return [
id: promocode?.id,
// all the fields you'd like to return
// in your JSON object
]
}
}
}
Then, inside of your Bootstrap.groovy file, include the following:
import packageName.marshallers.PromocodeMarshaller
def promodcodeMarshaller = new PromocodeMarshaller()
promocodeMarshaller.register()
For a full description, see this article.
Related
Specifically, I'm trying to improve deserialization in my Angular project. I have a base HTTP class that abstracts away some of the universal aspects of interacting with my REST service:
import { HttpClient, HttpParams } from '#angular/common/http';
export class BaseHttpService {
...
protected get<T>(url: string, params: object = {}, options: object = {}): Observable<any> {
// some operations every get request needs to do
return this.httpClient.get<T>(url, { params: httpParams, ...options });
}
...
And I'm using the cerialize library to handle object deserialization, specifically for Java Date (millisecond since epoch format) conversion to Javascript Date, and occasionally some helper methods that I want stored on the class (as opposed to just taking the JSON in through a Typescript interface). So my use of this method always looks something like:
import { Deserialize } from 'cerialize';
import { ModelA } from './models';
export class SomeControllerService {
...
constructor(private baseHttpService: BaseHttpService) {}
someMethod<ModelA>(): Observable<ModelA> {
return this.get<ModelA>().pipe(map(response => Deserialize(response, ModelA)));
}
...
What I want to do is remove the need for .pipe(map(... which ends up being in every single method that returns a REST response object and is basically the same, just with a different model depending on the endpoint. In Java, I believe you could use the generic in the base service as an input to the Deserialize function like (response => Deserialize(response, T.class)). I also want to avoid adding another parameter to the base get function. Per my title, I could also see a solution that refactors to calling a function on the generic, that the individual models could implement specific deserialization logic in, but again, since the generic is just a type, that doesn't seem possible. Am I just running up to a limitation of Typescript?
How can I return a SCALA list or sequence in Sprint #RestController. List return value is not being serialized properly.
The result is:
[GET] http://localhost:9090/devices
{"empty":false,"traversableAgain":true}
Do I need to import Jackson ObjectMapper com.fasterxml.jackson for proper REST get result serialization on a list?
My RestController looks like this:
#RestController
class DeviceController {
var devices = Set[Device]()
#RequestMapping(value = Array("/devices"), method = Array(RequestMethod.GET))
def accounts() : List[Device] = devices.toList
}
Spring was NOT designed with SCALA in mind - hence it cannot handle SCALA lists properly. Nor can it handle Seq[Device].
Just use SCALA's JavaConvertes package to easliy convert SCALA list to JAVA list.
import scala.collection.JavaConverters._
#RestController
class DeviceController {
var devices = Set[Device]()
#RequestMapping(value = Array("/devices"), method = Array(RequestMethod.GET))
def accounts() : java.util.List[Device] = {
devices.toList.asJava
}
}
and the result will be:
[GET] http://localhost:9090/devices
[{"name":"first device"},{"name":"second device"}]
Remember to change the result type to: java.util.List[Device]
Okay I have tried everything I know to custom render a one-to-many association with JSON views and failed miserably.
Here's what part of my ticket domain looks like (All good here) ...
class TttTicket {
String title
String number
String description
TttUser assignee
String priority
String status
TttUser creator
Date dateCreated
Date lastUpdated
static mappedBy = [subscribers : 'none', creator:'none']
static belongsTo = [project:TttProject, creator:TttUser]
static hasMany = [subscribers: TttUser]
... blar blar
}
Here's my associated gson rendering template...
import ttt_api_server.TttTicket
model {
TttTicket tttTicket
}
json g.render(tttTicket, [excludes:['creator','subscribers','project']]){
creator {
id tttTicket.creator.id
name tttTicket.creator.name
email tttTicket.creator.email
}
project{
id tttTicket.project.id
name tttTicket.project.name
}
}
... which is working nicely so far. I want to now restricted the output of the properties for each of the subscribers.
How do I rotate over these? For example...
import ttt_api_server.TttTicket
model {
TttTicket tttTicket
}
json g.render(tttTicket, [excludes:['creator','subscribers','project']]){
creator {
id tttTicket.creator.id
name tttTicket.creator.name
email tttTicket.creator.email
}
subscibers g.render(){
tttTicket.subscribers.each {sub ->
return {
name sub.name
}
}
}
project{
id tttTicket.project.id
name tttTicket.project.name
}
}
This does not seem to be documented anywhere. I would like to controller the JSON out put of each subscriber at this level. Not at the domain level as I may need to change the property output depending on my JSON requirements.
Please help :-(
If I understand this correctly, when you do a REST call on ticket, you wanted the domain object that is inside the hasMany relationship to show a specified properties (in this case its subscribers). But when you call directly on the subscribers, you wanted all the properties.
You can create a map first and then render the objects based on the map created (have a look at this).
In your case:
import ttt_api_server.TttTicket
model {
TttTicket tttTicket
}
json g.render(tttTicket, [excludes:['creator','subscribers','project']]){
creator {
id tttTicket.creator.id
name tttTicket.creator.name
email tttTicket.creator.email
}
def s = tttTicket.subsribers.collect {
[
id: it.id,
name: it.name
]
}
subscribers s //this part is doing the rendering
project{
id tttTicket.project.id
name tttTicket.project.name
}
}
If this is the way you want this object to be rendered whenever it's rendered as JSON, I suggest implementing ObjectMarshaller<JSON>:
class TttTicketMarshaller implements ObjectMarshaller<JSON> {
#Override
boolean supports(Object object) {
return object instanceof TttTicket
}
#Override
void marshalObject(Object object, JSON converter) throws ConverterException {
TttTicket ticket = (TttTicket)object
def jsonWriter = converter.writer
jsonWriter.object()
jsonWriter.key("title")
jsonWriter.value(ticket.title)
//other fields
jsonWriter.key("creator")
jsonWriter.object()
jsonWriter.key("id")
jsonWriter.value(ticket.creator.id)
//other creator fields
jsonWriter.endObject()
}
}
I'm not 100% sure about the object() and endObject() functionality, but that appears to be the correct use.
Once you have this, register this in your BootStrap.groovy:
def init = { servletContext ->
JSON.registerObjectMarshaller(new TttTicketMarshaller())
}
With all this in place, you can simply use
render myTicket as JSON
to return the JSON representation of the object as defined by your marshaller
Hi Say that I have a domain class
class Book{
static hasOne=[author:Author]
long id
String name
}
class Author {
static hasMany=[books:Book]
long id
String name
}
I have a json object sent in. Can I jus do a new Book(Json) and not manually set the property?
Using the built-in Grails JSON converter makes this easier
import grails.converters.JSON
class BookController {
def save = {
def book = new Book(JSON.parse(yourJson))
book.save(flush:true)
}
}
In the code what's happening (we're parsing a JSON object and setting the properties on the Book entity and saving
use the JsonBinder:
def json = request.JSON;
def book= new Book();
JsonBinder.bindJSON(book, json);
dont forget to import these packages:
import grails.converters.JSON;
import com.ocom.grails.JsonBinder;
Ok, I have a very simple app created in Grails.
I have a generated domain class (Person) and its generated controller, using the automatic Grails scaffold:
package contacts
class PersonController {
def scaffold = Person
}
Now I'd like to get a JSON representation of a Person object.
Do I have to change the view or the controller? And how?
Thank you.
Add the following to your controller:
def list = {
params.max = Math.min(params.max ? params.int('max') : 10, 100)
def personList = Person.list(params)
withFormat {
html {
[personInstanceList: personList, personInstanceTotal: Person.count()]
}
json {
render personList as JSON
}
}
}
This should support both your scaffolding and the JSON output.
You can access the scaffolding as:
http://localhost:8080/contacts/person/list
You can access the Person list as json with:
http://localhost:8080/contacts/person/list?format=json
There are other ways to do it too, but I like doing it this way to leave the scaffolding around for testing.