0

I have a simple problem and documentation is not helping me resolve it.

I have created a Grails v3.3.3 demo project - and created a simple domain class called JsonApiBook, with 'name' attribute like this

package ttrestapi

import grails.rest.*

@Resource (uri='/jsonApiBook', formats=['json','xml'])
class JsonApiBook {

    static constraints = {
    }

    String name
}

and marked up the URI as the documentation says the JSON API rendering only works with domain classes (and not a controller class).

In my bootstrap I have saved a instance of book to the tables - and can view that generally.

In my views directory I have a created jsonApiBook folder and created two gson files.

A '_jsonApIBook' template like this

import ttrestapi.JsonApiBook

model {
    JsonApiBook book
}
json jsonapi.render(book)

which invokes the jsonapi helper object to render the instance.

I have in the same directory created an index.json like this:

import ttrestapi.Book

model {
    List<Book> bookList
}

// We can use template namespace
// method with a Collection.
json tmpl.book(bookList) 

When I run the app and use postman or browser to render then I get a result but its Json api compliant (I think it's ignored the template).

So localhost:8080/jsonApiBook just returns (looks default layout):

[
    {
        "id": 1,
        "name": "json api book3"
    }
]

and localhost:8080/jsonApiBook/1 just returns 'null' which can't be right.

How should I be setting up the json views for rendering JSON API compliant output? As this doesn't appear to work correctly.

halfer
  • 19,824
  • 17
  • 99
  • 186
WILLIAM WOODMAN
  • 1,185
  • 5
  • 19
  • 36

2 Answers2

0

build.gradle

buildscript {
    ....

    dependencies {
      ........        
        classpath "org.grails.plugins:views-gradle:1.2.7"
    }
}

--

apply plugin: "org.grails.grails-web"
apply plugin: "org.grails.plugins.views-json"

dependencies {
. . .

compile "org.grails.plugins:views-json:1.2.7"
. . .
}

Domain JsonApiBook.groovy

import grails.rest.Resource

@Resource (uri='/jsonApiBook', formats=['json','xml'])
class JsonApiBook {

    String name
    static constraints = {
    }
}

Bootstrap.groovy

class BootStrap {

    def init = { servletContext ->

        new JsonApiBook(name: 'first').save(flush:true)
        new JsonApiBook(name: 'second').save(flush:true)
        new JsonApiBook(name: 'third').save(flush:true)
        new JsonApiBook(name: 'fourth').save(flush:true)
        new JsonApiBook(name: 'fifth').save(flush:true)
    }
    def destroy = {
    }
}

Created folder under view called jsonApiBook

Created template named _jsonApiBook.gson in jsonApiBook folder

model {
    JsonApiBook jsonApiBook
}
json {
    name jsonApiBook.name
}

created show.gson under same folder

model {
    JsonApiBook jsonApiBook

}
json g.render(template:"jsonApiBook", model:[jsonApiBook:jsonApiBook])

When i run http://localhost:8080/jsonApiBook i get bellow:

enter image description here

When i run http://localhost:8080/jsonApiBook/1 i get bellow:

enter image description here

Note: I used grails 3.3.3 with h2 memory DB

Reference Hope this helps you

Rahul Mahadik
  • 11,668
  • 6
  • 41
  • 54
0

ok - got to similar place today on the train. Essentially the convention over configuration is core to whats happening here.

First the @Resource annotation generates a default RestfulController for you. In this approach the default base template _resourceClassName.gson expects the model variable to have the same name as the resource type so my original example instead of 'book'

import ttrestapi.JsonApiBook

model {
    JsonApiBook book
}
json jsonapi.render(book)

it should really read as (following convention)

import ttrestapi.JsonApiBook

// variable should be same name as the Class name starting with lowercase
// as default (it can be different but the caller has to  change how the
// the template parameter is invoked 
model {
    JsonApiBook jsonApiBook
}
json jsonapi.render(jsonApiBook)

Then the index.gson should have read as modified below

import ttrestapi.JsonBookApi

//note although not obvious in the written docs which use the show command, the 
// default expected model variable is <resourceClass>List
model {
    List<JsonBookApi> jsonBookApiList  
}

// We can use template namespace
// method with a Collection.
json tmpl.jsonBookApi  (jsonBookApiList  ) 

If you want to use another variable name then in the base template you'd have to declare that name as map when calling the base template, from the index.gson . e.g. say the variable name in the base template was

model {
    JsonBookApi myBook...

then when calling this template from my index.gson you would put something like this

...
model {
    List<JsonBookApi> jsonBookApiList  
}

json tmpl.jsonBookApi  ("myBook", jsonBookApiList  ) 

this invokes the correct template _jsonBookApi, but takes the model variable default in the index.gson and forces it to bind the value of jsonBookApiList to the myBook variable in the base template (_jsonBookApi.gson).

With the default generation of a controller, using @Resource annotation, the model variable will always be 'resourceClassName'List

I think the only way to change that is not to use the @Resource annotation on your domain class, but to use the URL mappings configuration to map your uri to a controller, and then you have to create a controller yourself by hand and ensure you extend from RestfulController. doing this you can override the default model variable name by implementing an overidden 'index()' method and ensuring you explicitly name the model variable you want, and ensure that the index.gson model variable is exactly the same as that set in your controller.

however the key point was I was not following the core convention defaults so the code as originally built couldn't work and returned null.

when you start out the documentation isn't absolutely clear what bits are part of the convention, and in the examples (which use show.gson) don't tell you what the model variable default name will be for the index.gson (add List to end) so its quite easy to get lost

WILLIAM WOODMAN
  • 1,185
  • 5
  • 19
  • 36