如何使用 Grails 4 JSON 视图呈现域对象的映射
How do I render a map of domain objects using a Grails 4 JSON View
这是以下问题的后续:
我有以下 JSON 视图,我想使用 _breakfast.gson
模板呈现 mealsByPerson
地图的值。此外,我希望能够将 allCaps
模型属性从 _foo.gson
传递到 breakfast.gson
/foo/_foo.gson
import rendermapexample.Breakfast
model {
Float cost
Date date
Map<String, Breakfast> mealsByPerson
Boolean allCaps
}
json {
date date
cost cost
mealsByPerson g.render(mealsByPerson){} //HOW DO I PASS `allCaps` to this template?
// This doesn't work
// mealsByPerson g.render(mealsByPerson, model: [allCaps: true]){}
}
/breaskfast/_breaskfast.gson
import rendermapexample.Breakfast
model {
Breakfast breakfast
Boolean allCaps
}
json {
meat allCaps ? breakfast.meat.toUpperCase() : breakfast.meat
eggs allCaps ? breakfast.eggs.toUpperCase() : breakfast.eggs
side allCaps ? breakfast.side.toUpperCase() : breakfast.side
}
FooController
package rendermapexample
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,
allCaps: params.boolean("allCaps")
]
}
}
期望的输出
http://localhost:8080/foo
{
"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"
}
}
}
http://localhost:8080/foo?allCaps=true
{
"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"
}
}
}
示例项目
更新:请查看我的 .
这是我的(有点)老派方法:
First,因为 allCaps 要求可能不仅对那个特定的 controller/action 有用,我会向 [=13] 添加一个 asMap
方法=] 域 class 本身。如果它的参数 allCaps
为真,它会将所有字符串属性大写,并且 returns 具有所有对象属性的 Map
:
class Breakfast {
String meat
String eggs
String side
Integer number // Just another random propperty
static constraints = {
}
// Generic version, We asume we need allCaps for all String properties
def asMap(boolean allCaps=false) {
def breakfast = [:]
this.properties.each { key, value ->
if (value && value.class == String && allCaps == true) {
breakfast[key] = value.toUpperCase()
} else {
breakfast[key] = value
}
}
return breakfast
}
// An alternate/sillier version
def asMapSimpler(boolean allCaps=false) {
return [
meat:meat.toUpperCase(),
eggs:eggs.toUpperCase(),
side:side.toUpperCase(),
number: number
]
}
}
接下来,我们可以使用FooController
中的asMap
方法:
class FooController {
static responseFormats = ['json', 'xml']
def index() {
// default: false
def allCaps = params.boolean("allCaps") ?: false
Map<String, Map> mealsByPerson = [
Tom: new Breakfast(meat: "bacon", eggs: "scrambled", side: "hashbrowns").asMap(allCaps),
Jack: new Breakfast(meat: "sausage", eggs: "over easy", side: "pancakes").asMap(allCaps)
]
render template: "foo", model: [
cost: 12.34f,
date: new Date(),
mealsByPerson: mealsByPerson,
allCaps: allCaps
]
}
}
重温问题
我重新审视了这个问题和我以前的答案,并决定 post 这个而不是编辑前一个,在这里解决一些 comments/suggestions 收到的问题以改进我提出的解决方案。
我将表示逻辑从域 class 移开。为此,我探索了三种不同的方法:
- 使用 Grails 服务
- 在JSON视图端编码
- 仅使用模板
为了清楚起见,我在 URLMappings.groovy
中添加了以下映射:
"/approach1"(controller: 'foo', action:'approach1')
"/approach2"(controller: 'foo', action:'approach2')
"/approach3"(controller: 'foo', action:'approach3')
方法 1:使用 Grails 服务
请注意,在 FooController
中,服务 bean fooService
作为参数包含在对 JSON 视图的 respond
调用中。
FooService.groovy
package rendermapexample
import grails.gorm.transactions.Transactional
@Transactional
class FooService {
def toAllCaps(mealsByPerson) {
mealsByPerson.each { person, breakfast ->
def breakfastMap = [:]
breakfast.properties.each { key, value ->
if (value && value.class == String) {
breakfastMap[key] = value.toUpperCase()
} else {
breakfastMap[key] = value
}
}
mealsByPerson[person] = breakfastMap
}
return mealsByPerson
}
}
FooController.groovy
package rendermapexample
class FooController {
static responseFormats = ['json', 'xml']
def fooService
def approach1() {
Map<String, Breakfast> mealsByPerson = [
Tom: new Breakfast(meat: "bacon", eggs: "scrambled", side: "hashbrowns"),
Jack: new Breakfast(meat: "sausage", eggs: "over easy", side: "pancakes")
]
respond cost: 12.34f,
date: new Date(),
mealsByPerson: mealsByPerson,
allCaps: params.boolean("allCaps"),
fooService: fooService
}
}
approach1.gson
import rendermapexample.Breakfast
import rendermapexample.FooService
model {
Float cost
Date date
Map<String, Breakfast> mealsByPerson
Boolean allCaps
FooService fooService
}
json {
date date
cost cost
mealsByPerson g.render(allCaps ? fooService.toAllCaps(mealsByPerson) : mealsByPerson)
}
当然,我们可以将 toAllCaps
代码放入 POJO 静态实用程序 class,然后将其导入 [=27],而不是将 fooService
bean 作为参数传递=].
方法 2:在 JSON 视图端编码
如果 JSON View 端需要更多控制,我们可以将 toAllCaps
函数从 FooService.groovy
移动到 approach1.gson
,然后丢弃 FooService.groovy
.
FooController.groovy
package rendermapexample
class FooController {
static responseFormats = ['json', 'xml']
def approach2() {
Map<String, Breakfast> mealsByPerson = [
Tom: new Breakfast(meat: "bacon", eggs: "scrambled", side: "hashbrowns"),
Jack: new Breakfast(meat: "sausage", eggs: "over easy", side: "pancakes")
]
respond cost: 12.34f,
date: new Date(),
mealsByPerson: mealsByPerson,
allCaps: params.boolean("allCaps")
}
}
approach2.gson
import rendermapexample.Breakfast
model {
Float cost
Date date
Map<String, Breakfast> mealsByPerson
Boolean allCaps
}
json {
date date
cost cost
mealsByPerson g.render(allCaps ? toAllCaps(mealsByPerson) : mealsByPerson)
}
def toAllCaps(mealsByPerson) {
mealsByPerson.each { person, breakfast ->
def breakfastMap = [:]
breakfast.properties.each { key, value ->
if (value && value.class == String) {
breakfastMap[key] = value.toUpperCase()
} else {
breakfastMap[key] = value
}
}
mealsByPerson[person] = breakfastMap
}
return mealsByPerson
}
方法 3:仅使用模板
这里我打算少做一些传统的groovy编码,更多地依赖JSON视图模板,如官方文档中的outlined。
请注意以下注意事项:
我正在使用 mealsByPerson
的 ArrayList
变体,因为 JSON 视图模板的某些功能需要一个实现 Iterator
接口的对象. 重要提示: 这会导致 JSON 单独的单独对象数组,而不是单个 JSON 包含原始问题中所述的所有映射条目的对象。
我不得不为 JSON 视图禁用静态编译。这是因为 mealsByPerson
中的某些 JSON 对象名称是动态的(即它们不仅仅是标签,而是实际数据)。即来自原始 post.
的“Tom”和“Jack”对象名称
application.yml
grails:
views:
json:
compileStatic: false
FooController.groovy
package rendermapexample
class FooController {
static responseFormats = ['json', 'xml']
def approach3() {
ArrayList mealsByPerson = [
Tom: new Breakfast(meat: "bacon", eggs: "scrambled", side: "hashbrowns"),
Jack: new Breakfast(meat: "sausage", eggs: "over easy", side: "pancakes")
].collect()
respond cost: 12.34f,
date: new Date(),
mealsByPerson: mealsByPerson,
allCaps: params.boolean("allCaps")
}
}
approach3.gson
import rendermapexample.Breakfast
model {
Float cost
Date date
ArrayList mealsByPerson
Boolean allCaps
}
json {
date date
cost cost
mealsByPerson tmpl.mealsByPerson(mealsByPerson, [allCaps: allCaps])
}
_mealsByPerson.gson
import rendermapexample.Breakfast
model {
Map.Entry mealsByPerson
Boolean allCaps
}
String person = mealsByPerson.key
Breakfast breakfast = mealsByPerson.value
json {
"${person}" tmpl.breakfast(breakfast:breakfast, allCaps:allCaps)
}
_breakfast.gson
import rendermapexample.Breakfast
model {
Breakfast breakfast
Boolean allCaps
}
json {
if (allCaps) {
meat breakfast.meat.toUpperCase()
eggs breakfast.eggs.toUpperCase()
side breakfast.side.toUpperCase()
} else {
meat breakfast.meat
eggs breakfast.eggs
side breakfast.side
}
}
所以这终于奏效了
_foo.gson
import rendermapexample.Breakfast
model {
Float cost
Date date
Map<String, Breakfast> mealsByPerson
Boolean allCaps
}
json {
date date
cost cost
mealsByPerson tmpl."/foo/mealsByPerson2"([mealsByPerson: mealsByPerson, allCaps: allCaps])
}
_mealsByPerson2.gson
model {
Map mealsByPerson
Boolean allCaps
}
json {
for(Map.Entry entry : mealsByPerson) {
"${entry.key}" tmpl."/breakfast/breakfast"(breakfast:entry.value, allCaps:allCaps)
}
}
注意:这 不适用于 _mealByPerson.gson
您必须使用标准循环而不是 each
闭包才能工作
model {
Map mealsByPerson
Boolean allCaps
}
json {
mealsByPerson.each { Map.Entry entry ->
"${entry.key}" tmpl."/breakfast/breakfast"(breakfast:entry.value, allCaps:allCaps)
}
}
这是以下问题的后续:
我有以下 JSON 视图,我想使用 _breakfast.gson
模板呈现 mealsByPerson
地图的值。此外,我希望能够将 allCaps
模型属性从 _foo.gson
传递到 breakfast.gson
/foo/_foo.gson
import rendermapexample.Breakfast
model {
Float cost
Date date
Map<String, Breakfast> mealsByPerson
Boolean allCaps
}
json {
date date
cost cost
mealsByPerson g.render(mealsByPerson){} //HOW DO I PASS `allCaps` to this template?
// This doesn't work
// mealsByPerson g.render(mealsByPerson, model: [allCaps: true]){}
}
/breaskfast/_breaskfast.gson
import rendermapexample.Breakfast
model {
Breakfast breakfast
Boolean allCaps
}
json {
meat allCaps ? breakfast.meat.toUpperCase() : breakfast.meat
eggs allCaps ? breakfast.eggs.toUpperCase() : breakfast.eggs
side allCaps ? breakfast.side.toUpperCase() : breakfast.side
}
FooController
package rendermapexample
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,
allCaps: params.boolean("allCaps")
]
}
}
期望的输出
http://localhost:8080/foo
{
"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"
}
}
}
http://localhost:8080/foo?allCaps=true
{
"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"
}
}
}
示例项目
更新:请查看我的
这是我的(有点)老派方法:
First,因为 allCaps 要求可能不仅对那个特定的 controller/action 有用,我会向 [=13] 添加一个 asMap
方法=] 域 class 本身。如果它的参数 allCaps
为真,它会将所有字符串属性大写,并且 returns 具有所有对象属性的 Map
:
class Breakfast {
String meat
String eggs
String side
Integer number // Just another random propperty
static constraints = {
}
// Generic version, We asume we need allCaps for all String properties
def asMap(boolean allCaps=false) {
def breakfast = [:]
this.properties.each { key, value ->
if (value && value.class == String && allCaps == true) {
breakfast[key] = value.toUpperCase()
} else {
breakfast[key] = value
}
}
return breakfast
}
// An alternate/sillier version
def asMapSimpler(boolean allCaps=false) {
return [
meat:meat.toUpperCase(),
eggs:eggs.toUpperCase(),
side:side.toUpperCase(),
number: number
]
}
}
接下来,我们可以使用FooController
中的asMap
方法:
class FooController {
static responseFormats = ['json', 'xml']
def index() {
// default: false
def allCaps = params.boolean("allCaps") ?: false
Map<String, Map> mealsByPerson = [
Tom: new Breakfast(meat: "bacon", eggs: "scrambled", side: "hashbrowns").asMap(allCaps),
Jack: new Breakfast(meat: "sausage", eggs: "over easy", side: "pancakes").asMap(allCaps)
]
render template: "foo", model: [
cost: 12.34f,
date: new Date(),
mealsByPerson: mealsByPerson,
allCaps: allCaps
]
}
}
重温问题
我重新审视了这个问题和我以前的答案,并决定 post 这个而不是编辑前一个,在这里解决一些 comments/suggestions 收到的问题以改进我提出的解决方案。
我将表示逻辑从域 class 移开。为此,我探索了三种不同的方法:
- 使用 Grails 服务
- 在JSON视图端编码
- 仅使用模板
为了清楚起见,我在 URLMappings.groovy
中添加了以下映射:
"/approach1"(controller: 'foo', action:'approach1')
"/approach2"(controller: 'foo', action:'approach2')
"/approach3"(controller: 'foo', action:'approach3')
方法 1:使用 Grails 服务
请注意,在 FooController
中,服务 bean fooService
作为参数包含在对 JSON 视图的 respond
调用中。
FooService.groovy
package rendermapexample
import grails.gorm.transactions.Transactional
@Transactional
class FooService {
def toAllCaps(mealsByPerson) {
mealsByPerson.each { person, breakfast ->
def breakfastMap = [:]
breakfast.properties.each { key, value ->
if (value && value.class == String) {
breakfastMap[key] = value.toUpperCase()
} else {
breakfastMap[key] = value
}
}
mealsByPerson[person] = breakfastMap
}
return mealsByPerson
}
}
FooController.groovy
package rendermapexample
class FooController {
static responseFormats = ['json', 'xml']
def fooService
def approach1() {
Map<String, Breakfast> mealsByPerson = [
Tom: new Breakfast(meat: "bacon", eggs: "scrambled", side: "hashbrowns"),
Jack: new Breakfast(meat: "sausage", eggs: "over easy", side: "pancakes")
]
respond cost: 12.34f,
date: new Date(),
mealsByPerson: mealsByPerson,
allCaps: params.boolean("allCaps"),
fooService: fooService
}
}
approach1.gson
import rendermapexample.Breakfast
import rendermapexample.FooService
model {
Float cost
Date date
Map<String, Breakfast> mealsByPerson
Boolean allCaps
FooService fooService
}
json {
date date
cost cost
mealsByPerson g.render(allCaps ? fooService.toAllCaps(mealsByPerson) : mealsByPerson)
}
当然,我们可以将 toAllCaps
代码放入 POJO 静态实用程序 class,然后将其导入 [=27],而不是将 fooService
bean 作为参数传递=].
方法 2:在 JSON 视图端编码
如果 JSON View 端需要更多控制,我们可以将 toAllCaps
函数从 FooService.groovy
移动到 approach1.gson
,然后丢弃 FooService.groovy
.
FooController.groovy
package rendermapexample
class FooController {
static responseFormats = ['json', 'xml']
def approach2() {
Map<String, Breakfast> mealsByPerson = [
Tom: new Breakfast(meat: "bacon", eggs: "scrambled", side: "hashbrowns"),
Jack: new Breakfast(meat: "sausage", eggs: "over easy", side: "pancakes")
]
respond cost: 12.34f,
date: new Date(),
mealsByPerson: mealsByPerson,
allCaps: params.boolean("allCaps")
}
}
approach2.gson
import rendermapexample.Breakfast
model {
Float cost
Date date
Map<String, Breakfast> mealsByPerson
Boolean allCaps
}
json {
date date
cost cost
mealsByPerson g.render(allCaps ? toAllCaps(mealsByPerson) : mealsByPerson)
}
def toAllCaps(mealsByPerson) {
mealsByPerson.each { person, breakfast ->
def breakfastMap = [:]
breakfast.properties.each { key, value ->
if (value && value.class == String) {
breakfastMap[key] = value.toUpperCase()
} else {
breakfastMap[key] = value
}
}
mealsByPerson[person] = breakfastMap
}
return mealsByPerson
}
方法 3:仅使用模板
这里我打算少做一些传统的groovy编码,更多地依赖JSON视图模板,如官方文档中的outlined。
请注意以下注意事项:
我正在使用
mealsByPerson
的ArrayList
变体,因为 JSON 视图模板的某些功能需要一个实现Iterator
接口的对象. 重要提示: 这会导致 JSON 单独的单独对象数组,而不是单个 JSON 包含原始问题中所述的所有映射条目的对象。我不得不为 JSON 视图禁用静态编译。这是因为
的“Tom”和“Jack”对象名称mealsByPerson
中的某些 JSON 对象名称是动态的(即它们不仅仅是标签,而是实际数据)。即来自原始 post.
application.yml
grails:
views:
json:
compileStatic: false
FooController.groovy
package rendermapexample
class FooController {
static responseFormats = ['json', 'xml']
def approach3() {
ArrayList mealsByPerson = [
Tom: new Breakfast(meat: "bacon", eggs: "scrambled", side: "hashbrowns"),
Jack: new Breakfast(meat: "sausage", eggs: "over easy", side: "pancakes")
].collect()
respond cost: 12.34f,
date: new Date(),
mealsByPerson: mealsByPerson,
allCaps: params.boolean("allCaps")
}
}
approach3.gson
import rendermapexample.Breakfast
model {
Float cost
Date date
ArrayList mealsByPerson
Boolean allCaps
}
json {
date date
cost cost
mealsByPerson tmpl.mealsByPerson(mealsByPerson, [allCaps: allCaps])
}
_mealsByPerson.gson
import rendermapexample.Breakfast
model {
Map.Entry mealsByPerson
Boolean allCaps
}
String person = mealsByPerson.key
Breakfast breakfast = mealsByPerson.value
json {
"${person}" tmpl.breakfast(breakfast:breakfast, allCaps:allCaps)
}
_breakfast.gson
import rendermapexample.Breakfast
model {
Breakfast breakfast
Boolean allCaps
}
json {
if (allCaps) {
meat breakfast.meat.toUpperCase()
eggs breakfast.eggs.toUpperCase()
side breakfast.side.toUpperCase()
} else {
meat breakfast.meat
eggs breakfast.eggs
side breakfast.side
}
}
所以这终于奏效了
_foo.gson
import rendermapexample.Breakfast
model {
Float cost
Date date
Map<String, Breakfast> mealsByPerson
Boolean allCaps
}
json {
date date
cost cost
mealsByPerson tmpl."/foo/mealsByPerson2"([mealsByPerson: mealsByPerson, allCaps: allCaps])
}
_mealsByPerson2.gson
model {
Map mealsByPerson
Boolean allCaps
}
json {
for(Map.Entry entry : mealsByPerson) {
"${entry.key}" tmpl."/breakfast/breakfast"(breakfast:entry.value, allCaps:allCaps)
}
}
注意:这 不适用于 _mealByPerson.gson
您必须使用标准循环而不是 each
闭包才能工作
model {
Map mealsByPerson
Boolean allCaps
}
json {
mealsByPerson.each { Map.Entry entry ->
"${entry.key}" tmpl."/breakfast/breakfast"(breakfast:entry.value, allCaps:allCaps)
}
}