I'm trying to render a JSON view that has a property that is a Map
. I would like it to render in JSON as a sub-object. This seems like it should be very easy, but I can't find it documented anywhere. I've created an example project on GitHub.
class Breakfast {
String meat
String eggs
String side
}
class FooController {
static responseFormats = ['json', 'xml']
def index() {
Map<String, Breakfast> mealsByPerson = [
Tom: new Breakfast(meat: "bacon", eggs: "scrambled", side: "hashbrowns"),
Jack: new Breakfast(meat: "sausage", eggs: "over easy", side: "pancakes")
]
render template: "foo", model: [cost: 12.34f, date: new Date(), mealsByPerson: mealsByPerson]
}
}
/foo/_foo.gson
import rendermapexample.Breakfast
model {
Float cost
Date date
Map<String, Breakfast> mealsByPerson
}
json {
date date
cost cost
mealsByPerson mealsByPerson // HOW DO I RENDER THIS
}
{
"cost": 12.34,
"date": "2021-09-25T01:11:39Z",
"mealsByPerson": {
"Tom": {
"eggs": "scrambled",
"meat": "bacon",
"side": "hashbrowns"
},
"Jack": {
"eggs": "over easy",
"meat": "sausage",
"side": "pancakes"
}
}
}
java.lang.reflect.InvocationTargetException: null
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.grails.core.DefaultGrailsControllerClass$ReflectionInvoker.invoke(DefaultGrailsControllerClass.java:211)
at org.grails.core.DefaultGrailsControllerClass.invoke(DefaultGrailsControllerClass.java:188)
at org.grails.web.mapping.mvc.UrlMappingsInfoHandlerAdapter.handle(UrlMappingsInfoHandlerAdapter.groovy:90)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at org.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:77)
at org.grails.web.filters.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:67)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: org.grails.web.servlet.mvc.exceptions.ControllerExecutionException: Error rendering view: null
at grails.artefact.controller.support.ResponseRenderer$Trait$Helper.renderViewForTemplate(ResponseRenderer.groovy:617)
at grails.artefact.controller.support.ResponseRenderer$Trait$Helper.render(ResponseRenderer.groovy:353)
at rendermapexample.FooController.index(FooController.groovy:12)
... 15 common frames omitted
Caused by: grails.views.ViewRenderException: Error rendering view: null
at grails.views.AbstractWritableScript.writeTo(AbstractWritableScript.groovy:43)
at grails.views.mvc.GenericGroovyTemplateView.renderMergedOutputModel(GenericGroovyTemplateView.groovy:73)
at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:317)
at grails.artefact.controller.support.ResponseRenderer$Trait$Helper.renderViewForTemplate(ResponseRenderer.groovy:614)
... 17 common frames omitted
Caused by: java.lang.StackOverflowError: null
at grails.plugin.json.converters.InstantJsonConverter.handles(InstantJsonConverter.groovy:18)
at grails.plugin.json.builder.DefaultJsonGenerator.findConverter(DefaultJsonGenerator.java:457)
at grails.plugin.json.builder.DefaultJsonGenerator.writeObject(DefaultJsonGenerator.java:195)
at grails.plugin.json.builder.DefaultJsonGenerator.writeMapEntry(DefaultJsonGenerator.java:419)
...
The render statement below creates the JSON I want in this simple example,
but my real-world problem needs more control over rendering than as JSON
provides
render ([cost: 12.34f, date: new Date(), mealsByPerson: mealsByPerson] as JSON)
https://github.com/tonyerskine/rendermapexample
I posted a related question here in response to a comment below: How do I render a map of domain objects using a Grails 4 JSON View
If you change _foo.gson
to the following:
import rendermapexample.Breakfast
model {
Float cost
Date date
Map<String, Breakfast> mealsByPerson
}
json {
date date
cost cost
mealsByPerson g.render(mealsByPerson) {}
}
The response will include the following:
{
"cost": 12.34,
"date": "2021-09-27T17:24:14Z",
"mealsByPerson": {
"Jack": {
"eggs": "over easy",
"meat": "sausage",
"side": "pancakes"
},
"Tom": {
"eggs": "scrambled",
"meat": "bacon",
"side": "hashbrowns"
}
}