0

I am struggling with getting JSON API, and rendering of json for domain classes. I am using Grails v3.3.3 and JsonViews 1.2.7

If I expose a domain class by using @Resource annotation this automatically generates a controller and 'default' json output.

However I want some control over what's happening, so I have a another domain class (Customer, and Site in my demo app, where Customer hasMany Sites).

I do not use the @Resource annotation in either of these domain classes.

I manually create two controllers called CustomerRestController and SiteRestController by hand - and use the UrlMappings to expose the URI. (one using the '(resources: controller)' form and the other with declared explicit controller and actions:

class UrlMappings {

    static mappings = {

        "/api/customers"(resources:"customerRest")

        //or try to declare with explicit mappings
        "/api/sites" (controller: "siteRest", action: "index")
        "/api/sites/$id" (controller: "siteRest", action: "show", view: "show")

        "/$controller/$action?/$id?(.$format)?"{
            constraints {
                // apply constraints here
            }
        }

        "/"(view:"/index")
        "500"(view:'/error')
        "404"(view:'/notFound')
    }
}

So lets look first at what happens for SiteRestController (with simple debug println to show it's being called in the console). I have used the GORM data service model and just defined an interface in services and let Grails auto build the serviceImpl and then inject this into the controller. I have declared the response format to be [json]

@CompileStatic
class SiteRestController extends RestfulController {

    static responseFormats = ['json']

    SiteService siteService

    SiteRestController() {
        super (Site)
        println "siteRest default constructor controller created"
    }


    def index (Integer max)/*(@PathVariable String manufacturerName)*/ {
        println ("site.index: invoked index action on customer restful controller ")
        respond siteService.list([:]), model:[siteCount: siteService.count()]

    }


    def show(Long id) {
        println "site.show: show on SiteRestController called with $id"
        respond siteService.get(id)
    }


}

I have manually created /views/site/_site.gson, index.gson and show.gson

//_site.gson
import model.Site
println "_site template called "  //added to check that template is being called 

model {
    Site site
}

json jsonapi.render (site)

to show that view is being ignored I've edited the show.gson as follows

import model.Site

model {
    Site site
}

json g.render ([name:"will"])

and the index.gson view as

import model.Site

model {
    List<Site> siteList
    Long siteCount
}
println "index.gson more data"
json {
    name  "hello william"
}

So when I fire up the app and invoke the '/api/sites' url I get this. Which is just a standard rendering and doesn't use the _site.gson - and ignores my index.gson

[
    {
        "id": 1,
        "name": "headoffice",
        "customer": {
            "id": 1
        }
    }
]

And if I call '/api/sites/1' i get the following (this has clearly invoked the the _sites.gson template, but my 'show.gson' was modified to not use template and this is clearly being ignored)

_site template called 
{
    "data": {
        "type": "site",
        "id": "1",
        "attributes": {
            "name": "headoffice"
        },
        "relationships": {
            "customer": {
                "links": {
                    "self": "/customer/show/1"
                },
                "data": {
                    "type": "customer",
                    "id": "1"
                }
            }
        }
    },
    "links": {
        "self": "/site/show/1"
    }
}

I also tried to do a deep rendering from customers view template like this

import model.Customer


model {
    Customer customer
}

json jsonapi.render (customer, [deep:true, jsonApiObject:true])

which ignores my sites details on deep rendering when query /api/customers/1. Notice the site just shows the default id and doesn't seem to call the _site.gson:

{
    "jsonapi": {
        "version": "1.0"
    },
    "data": {
        "type": "customer",
        "id": "1",
        "attributes": {
            "name": "hsbc"
        },
        "relationships": {
            "sites": {
                "data": [
                    {
                        "type": "site",
                        "id": "1"
                    }
                ]
            }
        }
    },
    "links": {
        "self": "/customer/show/1"
    }
}

So I'm not understanding what's going one here. Grails has its own internal generated defaults and is ignoring the views etc that I have provided and doesn't seem to be doing the deep rendering when requested to do so.

Would anyone advise what why my views/templates are not being called as expected?

halfer
  • 19,824
  • 17
  • 99
  • 186
WILLIAM WOODMAN
  • 1,185
  • 5
  • 19
  • 36
  • There are a bunch of possible explanations. Does your project express a dependency on the json views library? – Jeff Scott Brown Mar 19 '18 at 00:32
  • hi jeff, yes I believe so - i have ' classpath "org.grails.plugins:views-gradle:1.2.7" ' in the buildscript , and ' compile "org.grails.plugins:views-json:1.2.7" ' in the dependencies, and 'apply plugin:"org.grails.plugins.views-json" ' in the project. I'm away in salford on work this week - so just about to recreate the project on my laptop as i forgot to put it up on git hub. – WILLIAM WOODMAN Mar 19 '18 at 09:13
  • Jeff re created and got to same problem I think. I have used UrlMappings to expose my RestEndpoint using "api/customers"(resources:"customerRest"). I can see this is invoked as i have a println action in the controller in the index and show methods so i know the right controller and methods are being called. I have updated the _customer.gson like this 'json.defaultTemplate { name "william" }'. when invoked from postman with /api/customers/1 and i can the _customer.gson template is being rendered (although my show.gson tries to render smothing else) – WILLIAM WOODMAN Mar 19 '18 at 11:00
  • in addition a added the view explicitly to the response like this ' respond customerService.get(id), view:"fred" ', and the same on the index action like this ' respond customerService.list(params), [model:[customerCount: customerService.count()], view:"fred"] ' where the view 'fred.gson' doesnt exist. when you invoke the URI from postman '/api/customers/1' uses the default _customer.gson ive provided and the '/api/customers' ignores that and renders result by some other means. – WILLIAM WOODMAN Mar 19 '18 at 11:04
  • i've posted this copy to giuthub here "https://github.com/woodmawa/ttRestApi" so you can see the code as it presently sits (i've used standard web profile and added the json views elements on top as i wanted the standard web gsps and the rest endpoints in the same project ). I cant figure out why the rest behaviour is ignoring my instructions and using its 'own' internal stuff. appreciate any light you can through on this – WILLIAM WOODMAN Mar 19 '18 at 11:09
  • Looks like that is either a private repo or has since been deleted. That URL is 404'ing for me. – Jeff Scott Brown Mar 19 '18 at 14:40
  • weird - did same to me - i've just pushed to git from intellij and tried to re read it https://github.com/woodmawa/ttRestApi and it showed me the project. Just opened in second browser just in case. Hopefully you can get it now? Can also access now on my phone. very odd – WILLIAM WOODMAN Mar 19 '18 at 16:10
  • The link works now – Jeff Scott Brown Mar 19 '18 at 16:15
  • just noted that the first URL seems to have included the ' " ' at the end of the url - and hence a 404. if you remove the " it should (i hope) go to the project. second copy of the url above doesn't have the " – WILLIAM WOODMAN Mar 19 '18 at 16:17
  • There are numerous things in the way that project code is written that don't make sense to me. I am sorry that I can't help. Best of luck! – Jeff Scott Brown Mar 19 '18 at 16:19
  • its a pretty basic try out and to show that the response action in the controller seems to be ignoring the index and show.gson files and using its own stuff. I tried to force it through the named views and that these get ignored. rather the library appears to doing its own thing. basically i cant get JSON API export to work as suggested in the documentation and this is part ( i think) of the problem – WILLIAM WOODMAN Mar 19 '18 at 17:23
  • You said "so when i fire up the app and invoke the '/api/sites' url i get this. Which is just a standard rendering and doesn't use the _site.gson - and ignores my index.gson". `/api/sites` is mapped to the index action in your `SiteRestController`. There is no `grails-app/views/siteRest/` folder. What `index.gson` do you think should be rendered there? There is so much in your question and so much unrelated code in your app that it is difficult to tell where the confusion is. – Jeff Scott Brown Mar 19 '18 at 17:43
  • Jeff i think its understanding how the grails convention is running. I understood the views directory names would have been the same as the domain class name - hence - i created views folders for 'customer' and 'site'. because i have both gsp views and gson views on the go this seemed like a safe assumption. so i have two controllers one for rest/gson only and one for ordinary gsp's. if in fact the convention is that the view folder name matches the controllerName then i have misunderstood what it assumes internally. – WILLIAM WOODMAN Mar 20 '18 at 14:28
  • i'll go back and try and create a customerRest and siteRest view folders (following the controller name idea) and see what happens then. what i had hoped to achieve was using ordinary gsp views (bau norm) and expose rest based views under a '/api/resource' uri so that i had the options of both exposures. I hope that made sense. – WILLIAM WOODMAN Mar 20 '18 at 14:30
  • "I understood the views directory names would have been the same as the domain class name" - The view directory names should correspond to the controller name, which may or may not be the same as domain class names. – Jeff Scott Brown Mar 20 '18 at 14:35
  • Jeff, that appears to be working. I have renamed my views to be named the same as the controllers so CustomerRestController, now has a view called CustomerRest with the gson underneath and that now seems to call correctly. thank you for that. so this misunderstanding is closed. however i now get stackoverflow if i try and do a [deep:true] option. so i'll raise a separate question for that issue. Thanks so much for the clarification. Think i've misunderstood that convention for years, i always though the view folders reflected the names of the domain classes - not the controller names. – WILLIAM WOODMAN Mar 20 '18 at 16:16

0 Answers0