繁体   English   中英

如何使用 Grails 4 JSON 视图呈现域对象的地图

[英]How do I render a map of domain objects using a Grails 4 JSON View

这是以下问题的后续:如何在 Grails 4 JSON 视图中将地图渲染为属性

我有以下 JSON 视图,并且我想使用_breakfast.gson模板呈现mealsByPerson地图的值。 另外,我希望能够将allCaps模型属性从_foo.gsonbreakfast.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"
        }
    }
}

示例项目

https://github.com/tonyerskine/rendermapexample

更新:请查看我改进的答案

这是我的(某种)老派方法:

首先,因为 allCaps 要求可能不仅对那个特定的控制器/动作有用,所以我会向Breakfast域类本身添加一个asMap方法。 如果参数allCaps为真,则将所有 String 属性大写,并返回一个包含所有对象属性的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
        ]
    }

}

接下来,我们可以使用asMap的方法FooController

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
        ]
    }
}

重新审视问题

我重新审视了这个问题和我之前的答案,并决定发布这个而不是编辑前者,在这里解决一些收到的评论/建议以改进我提出的解决方案。

我将表示逻辑从域类中移开。 为此,我探索了三种不同的方法:

  1. 使用 Grails 服务
  2. 在 JSON 视图端编码
  3. 仅使用模板

为了清楚起见,我在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
        
    }
}

方法1.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)
}

当然,不是将fooService bean 作为参数传递,我们可以将toAllCaps代码放入 POJO 静态实用程序类,然后将其导入到approach2.gson

方法二:在JSON View端编码

如果在 JSON 视图方面需要更多控制,我们可以将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")
        
    }
}

方法2.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:仅使用模板

在这里,我打算做的不那么传统的常规编码,并更多地依靠JSON视图模板,列出官方文档。

请注意以下注意事项:

  1. 我使用的是mealsByPersonArrayList变体,因为 JSON 视图模板的某些功能需要一个实现Iterator接口的对象。 重要提示:这会产生一个 JSON 数组,其中包含单独的单个对象,而不是包含原始问题中描述的所有地图条目的单个 JSON 对象。

  2. 我不得不禁用 JSON 视图的静态编译。 这是因为mealsByPerson中的一些 JSON 对象名称是动态的(即它们不仅仅是标签而是实际数据)。 即原始帖子中的“汤姆”和“杰克”对象名称。

应用程序.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")
    }
}

方法3.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)
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM