So, I am writing to write a Grails application when I discovered this new way to present JSON objects - using Grails GSON Views!
I haven't used Grails since version 2.x and now I jumped to version 5.1.1 – that's why GSON view is new to me!
GSON Basics
Basically, a GSON view works like a GSP view but we will write code using a JSON-like DSL instead of something HTML-like.
For example we have this controller:
class UserController {
def show(Long id) {
respond new User(
id: 1,
firstName: 'Johnny',
lastName: 'Appleseed',
emailAddress: '[email protected]'
)
}
}
Then we have the corresponding GSON file in grails-app/views/user/show.gson
:
model {
User user
}
json {
id user.id
fullName "${user.firstName} ${user.lastName}"
emailAddress user.emailAddress
}
With that, calling the endpoint will result a JSON that looks like the one in the GSON file.
{
"id": 1,
"fullName": "Johnny Appleseed",
"emailAddress": "[email protected]"
}
Grails views has a great documentation here and it shows different ways to render different objects using GSON. The problem here is I can't find in the documentation on how to properly render a Map
. The goal here is to show the URL mappings of my Grails application dynamically.
{
"message": "Welcome to my API!",
"version": "0.1-dev",
"urlMappings": {
"/": {
"*": "index"
},
"/users": {
"GET": "showAll",
"POST": "save"
},
"/users/(*)": {
"GET": "show",
"PUT": "update",
"DELETE": "delete"
}
}
}
All I can see in the documentation is how to render a List
, Set
, or any linear collections in GSON.
json(1,2,3) == "[1,2,3]"
json { name "Bob" } == '{"name":"Bob"}'
json([1,2,3]) { n it } == '[{"n":1},{"n":2},{"n":3}]'
First, I tried call(Iterable, Closure)
method with the given Map
as the 1st parameter. This did not work or even compile since Map
is not an Iterable
, according to the docs.
json {
urlMappings urlMappingData, {
// ... mapping logic ...
}
}
Map
object using GSON. The Grails app did not start due to compilation error.Second, I tried using the .entrySet()
and .keySet()
methods of the Map
interface to make it fit with the Iterable
requirement.
json {
urlMappings urlMappingData.entrySet(), { entry ->
def url = entry.key
def mappingsList = entry.value
// ... mapping logic ...
}
}
Map
object using GSON. Using this call(Iterable, Closure)
method just maps the collection to a list.This made the compilation errors disappear, but I still didn't get the JSON format I wanted to show. The Map
should be presented with key-value pairs from the values stored in it and not create a List
from a Map
!
Create a new Map
While thinking of a solution, I remembered how we do JSON marshalling back in Grails 2: create a Map
in the form of the JSON we want to present.
To do that, I used Groovy's collection manipulation methods such as .dropWhile()
, .groupBy()
, and especially .collectEntries()
– which creates a Map
from the size-2 (key, value) list that its closure returns. Now the GSON file looks like this:
model {
UrlMappingsHolder urlMappingsHolder
}
json {
message 'Welcome to my API!'
version '0.1-dev'
// creating the Map...
def urlMappingData = urlMappingsHolder.urlMappings // get URL mappings
.dropWhile { it.controllerName == null } // show only controller endpoints
.groupBy { it.urlData.urlPattern } // group with same URLs
.collectEntries { key, value -> [
key, // key is still the URL
value.collectEntries {
[(it.httpMethod): it.actionName] // method-action as the key-value pair
}
]}
urlMappings urlMappingData // present the created map in JSON
}
show.gson
file that builds the urlMappingData
map in it and then showing it in a JSON field.With the GSON file above, I can now show the URL mapping data with the format that I want it to be!
{
"message": "Welcome to my API!",
"version": "0.1-dev",
"urlMappings": {
"/": {
"*": "index"
},
"/users": {
"GET": "showAll",
"POST": "save"
},
"/users/(*)": {
"GET": "show",
"PUT": "update",
"DELETE": "delete"
}
}
}
I hope this helps anyone that needs to render a Map to their Grails GSON view!