How to exclude property of a collection in JSON rendering in Grails 2.3 - rest

I am trying to setup a rest webservice (JSON) this is what I am getting:
{"name":"test","routines":[{"class":"Routine","id":1},{"class":"Routine","id":2}]}
This is what I want to get:
{"name":"test","routines":[{"name": "routine-1"},{"name": "routine-2"}]}
I have these domains:
class Program {
String name;
static hasMany = [routines: Routine]
}
class Routine {
String name
}
I have this controller:
class ProgramController extends RestfulController {
static responseFormats = ['json']
def show(Program program) {
respond program
}
}
I added this in the resources.groovy
programRenderer(JsonRenderer, Program) {
excludes = ['class', 'id']
}
routineRenderer(JsonRenderer, Routine) {
excludes = ['class', 'id']
}
How do I include the name property of Routine in the json response using the show method/action of ProgramController?

The ObjectMarshaller approach is the technically correct way. However, the code is cumbersome to write and it's a maintenance headache syncing the fields of the domain with the marshaller.
In the spirit of being Groovy and keeping things really simple, we've been quite happy just adding a little out() method to each REST domain.
Program.groovy
class Program {
String name
static hasMany = [routines: Routine]
def out() {
return [
name: name,
count: routines?.size(),
routines: routines?.collect { [name: it.name] }
]
}
}
ProgramController.groovy
import grails.converters.JSON
class ProgramController {
def show() {
def resource = Program.read(params.id)
render resource.out() as JSON
}
}
JSON Response
{
name: "test",
count: 2,
routines: [{ name: "routine-1" }, { name: "routine-2" }]
}
The out() method approach makes it easy to customize the response JSON, such as adding count for the number of routines.

Related

What happens behind the scene when you update a nested array element in a MongoDB Document

When I do a nested object update inside an array in a Document. Does Mongo DB Engine needs to fetch and parse the whole document update the field and reinsert the document ?
db.ControllerPointCollection.updateOne({
"_id": "Ashutosh Das_MigrationTest_0_1_0"
}, {
$set: {
"Tables.$[t].Blocks.$[b].Points.$[p].Description": "Hey You"
}
}, {
arrayFilters: [{
"t.ID": 32
}, {
"b.ID": 268
}, {
"p.PointDefinitionID": 280
}]
})
Behind the scene, mongodb has a class called Model and inside Model class compose other behaviours with initializing other classes and one of them, I call it Sync which is implemented like this. this is not exact code, but you get the idea:
interface HasId {
id?: number; //optional
}
export class ApiSync<T extends HasId> {
constructor(public rootUrl: string) {}
// if user has Id, that means it is already stored in db, so we make a put request, if it does not then we make post
// so in mongoose, saving means Http request to db
save(data: T): AxiosPromise {
const { id } = data;
if (id) {
return axios.put(this.rootUrl + id, data);
} else {
return axios.post(this.rootUrl, data);
}
}
fetch(id: number): AxiosPromise {
return axios.get(this.rootUrl + id);
}
}

Grails RestfulController doesn't respond with a JSON when Content-Type: application/x-www-form-urlencoded header is present

I'm trying to implement a simple RestfulController for my application.
Given the following domain class:
class Test {
String name
int someInteger
static constraints = {
}
}
and its controller:
class TestController extends RestfulController<Test>{
TestController() {
super(Test)
}
}
Inside conf/UrlMappings.groovy I added the following entries:
"/api/$controller?(.${format})?" {
action = [POST: "save", PUT: "save", GET: "index", DELETE:"error"]
}
"/api/$controller/$id?(.${format})?" {
action = [POST: "update", PUT: "update", GET: "show", DELETE: "delete"]
}
Get requests are working fine, but Post and Put requests to a URL like http://localhost:8080/app/api/test.json when the Content-Type: application/x-www-form-urlencoded Header is present fail to respond with a JSON as expected. Instead render the show action view after persisting the entrie sent.
I also tried to use the Header Accept: application/json with no effect.
How can I fix that?
Edit:
Further investigating RestfulController's source file and the docs section regarding Content Negotiation I was able fix it by overriding the save and update methods replacing the line:
request.withFormat {
with:
withFormat {
Is it intentional or is there a flaw on RestfulController's implementation?
Why does it consider the Content-Type header instead of the Accept header to render response?
If it's acceptable for all your controller's methods to always respond with JSON (when there a response body), you can achieve this with responseFormats like so:
class TestController extends RestfulController<Test>{
static responseFormats = ['json']
TestController() {
super(Test)
}
def customJsonAction() {
respond Something.get(params.id)
}
def someActionThatRendersGsp() {
render view: 'myGsp', model: [foo: 'bar']
}
}
This means the controller will always respond with JSON regardless of which headers, params, etc. are sent by the client.
Sorry for taking so long to respond. I had some trouble putting everything to work. Thanks a lot #Dónal for all the help. Ended using the following class to do the trick:
import org.codehaus.groovy.grails.web.servlet.HttpHeaders;
import org.springframework.http.HttpStatus;
import grails.artefact.Artefact;
import grails.rest.RestfulController;
import grails.transaction.Transactional;
#Artefact("Controller")
#Transactional(readOnly = true)
class MyRestfulController<T> extends RestfulController<T> {
public MyRestfulController(Class<T> resource, boolean readOnly = false) {
super(resource, readOnly);
}
#Override
#Transactional
def save() {
if(handleReadOnly()) {
return
}
T instance = createResource(getParametersToBind())
instance.validate()
if (instance.hasErrors()) {
respond instance.errors, view:'create' // STATUS CODE 422
return
}
instance.save flush:true
def formatHolder = params.format ? this : request
formatHolder.withFormat {
form multipartForm {
flash.message = message(code: 'default.created.message', args: [message(code: "${resourceName}.label".toString(), default: resourceClassName), instance.id])
redirect instance
}
'*' {
response.addHeader(HttpHeaders.LOCATION,
g.createLink(
resource: this.controllerName, action: 'show',id: instance.id, absolute: true,
namespace: hasProperty('namespace') ? this.namespace : null ))
respond instance, [status: HttpStatus.CREATED]
}
}
}
#Override
#Transactional
def update() {
if(handleReadOnly()) {
return
}
T instance = queryForResource(params.id)
if (instance == null) {
notFound()
return
}
instance.properties = getParametersToBind()
if (instance.hasErrors()) {
respond instance.errors, view:'edit' // STATUS CODE 422
return
}
instance.save flush:true
def formatHolder = params.format ? this : request
formatHolder.withFormat {
form multipartForm {
flash.message = message(code: 'default.updated.message', args: [message(code: "${resourceClassName}.label".toString(), default: resourceClassName), instance.id])
redirect instance
}
'*'{
response.addHeader(HttpHeaders.LOCATION,
g.createLink(
resource: this.controllerName, action: 'show',id: instance.id, absolute: true,
namespace: hasProperty('namespace') ? this.namespace : null ))
respond instance, [status: HttpStatus.OK]
}
}
}
}
By using def formatHolder = params.format ? this : request and then call formatHolder.withFormat I am now able to override the response format independently from the request format.
It doesn't work for the Accept Header yet but at least it works.

Making a mixin of interfaces in Typescript

I have a class with over 80 methods, and each method accepts an object containing some defined interface.
class Stuff {
/* many more */
getAccount(req: IAccount, callback: ICallback) {
return this._call('getAccount', req, callback);
}
getIds(req: IIDs, callback: ICallback) {
return this._call('getIds', req, callback);
}
/* many more */
}
pretty 'boring' stuff, since it's just mapping to the underlaying _call method and making it type safe for each of the methods.
But sometimes these req param objects are made up from 2 interfaces or more, and instead of creating another interface for each time there's an "awkward", like this:
export interface ILoled extends IAccount {
loled: boolean;
}
export interface IRofloled extends ILoled {
rofled: boolean;
}
class Stuff {
getLols(req: ILoled){
}
getRofls(req: IRofloled){
}
}
is there any way I can just put it as an "inline" mixin of interfaces inside the method parameter list? like (which obviously don't work):
class Stuff {
getMoreStuff(req: <{} extends IAccount, ITime>) {
}
}
Yes you can, as of Typescript 1.6. Called Intersection types, use the & operator to combine types.
function extend<T, U>(first: T, second: U): T & U {
let result = <T & U> {};
for (let id in first) {
result[id] = first[id];
}
for (let id in second) {
if (!result.hasOwnProperty(id)) {
result[id] = second[id];
}
}
return result;
}
var x = extend({ a: "hello" }, { b: 42 });
x.a; // works
x.b; // works
is there any way I can just put it as an "inline" mixin of interfaces inside the method parameter list
No. You cannot extend an interface inline

How to obtain JSON (or XML) response from Grails?

I have 1 domain class, 1 controller, 1 URL mapping (see below). I want to send request, and receive JSON response.
But currently my request is correctly mapped to the correspondent controller method, the method is successfully executed, and then I get an error with message that jsp-file not available.
How to explain Grails, that I don't need no jsp-files: I want to receive/parse JSON requests, and send JSON responses right from my controllers?
class Brand {
String name
String description
String logoImageURL
static constraints = {
name(blank: false)
}
}
---------------------------
class UrlMappings {
static mappings = {
"/brands"(controller: "brand", parseRequest: true, action: "list")
}
}
---------------------------
import grails.converters.JSON
class BrandController {
def list = {
return Brand.list() as JSON
}
def show = {
return Brand.get(params.id) as JSON
}
def save = {
def brand = new Brand(name: params.name, description: params.description, logoImageURL: params.logoURL)
if (brand.save()) {
render brand as JSON
} else {
render brand.errors
}
}
}
============== Error message ===============
Request URL: http://localhost:8080/ShoesShop/brands
message /ShoesShop/WEB-INF/grails-app/views/brand/list.jsp
description The requested resource (/ShoesShop/WEB-INF/grails-app/views/brand/list.jsp) is not available.
It should work if you instead do:
def list = {
render Brand.list() as JSON
}

Grais Rest: get a Json object from a "lastName" and not "id"

How get a user of this class, from a lastName, not from the id.
in my example i use a REST web service.
My class USER.groovy in Grails:
class User {
String firstName
String lastName
String zipcode }
class UrlMappings.groovy
class UrlMappings {
static mappings = {
/user/$id" (controller: "user") {
action = [GET: "show"]
}
}
}
def show in UserController.groovy
def show = {
User user = User.get(params.id)
if (user) {
render user as JSON
} else {
SendNotFoundResponse()
}
}
As I understand, your problem that you don't know how to query domain by other fields that id. For current example you can use:
User.findByFirstName(params.id)
And, please read about GORM querying - http://grails.org/doc/latest/guide/GORM.html#querying