Grails LinkedHashMap到JSON保留RoundTrip上的订单

时间:2014-01-07 18:59:29

标签: json grails gsp

TL;博士

我需要将GSP模板中LinkedHashMap的数据发送到Controller并保留元素的顺序。

我假设像JSON这样的结构化数据格式是执行此操作的理想方式,但Grails的JSON转换器不会从LinkedHashMap创建有序的JSON对象。

将LinkedHashMap数据结构从GSP发送到Controller的最佳方法是什么,这样我可以保留顺序,但在解析数据时只做最少的工作?

长版

我正在开发一个taglib来在表格中呈现搜索结果。

在taglib中,我构造了一个LinkedHashMap,它指定了数据列和用户想要为列名显示的标签。例如:

def tableFields = [firstName: "First Name", lastName: "Surname", unique_id: "Your Whizbang ID"]

该地图被发送到视图,然后视图将其发送回控制器以从数据库中检索搜索结果。我需要保留元素的顺序(因此使用LinkedHashMap)。

我的第一个想法是将LinkedHashMap转换为JSON字符串,然后通过隐藏的表单元素将其发送到控制器。所以,

import grails.converters.JSON
//taglib class and other code
def tableFields = [firstName: "First Name", lastName: "Surname", unique_id: "Your Whizbang ID"] as JSON

但是,这会在HTML中创建这样的JSON对象。我把它放在隐藏字段的值属性中。

<input type="hidden" name="columns" value="{"firstName": "First Name", "lastName": "Surname", "unique_id": "Your Whizbang ID"}" id="columns">

这是JSON对象本身。

{"firstName": "First Name", "lastName": "Surname", "unique_id": "Your Whizbang ID"}

您可以看到JSON字符串的属性与JSON字符串中的LinkedHashMap的顺序相同。但是,JSON对象实际上不应该保留其属性的保留顺序。因此,当我的控制器收到columns参数,并在其上使用JSON.parse()方法时,它会创建一个普通的无序HashMap而不是LinkedHashMap。因此,当我将它们渲染到HTML表格中时,搜索结果中的列显示的顺序错误。

At least one fellow遇到了类似的问题。在运行JSON.parse()之后添加as LinkedHashMap不会削减它,因为.parse()方法会搞定从get go开始的顺序。

Daniel Woods在回复上述帖子时指出:

  

如果grails数据绑定器无法正常工作,您应该能够覆盖隐式属性设置器,将对象强制转换为您喜欢的Map实现。

我认为他说我可以编写自己的解析器,它将遵循JSON元素的顺序(即使它在技术上不应该)。我想我也可以编写自己的转换器,以便生成的JSON元素类似于:

{[{firstName: "First Name"}, {lastName: "Surname"}, {unique_id "Your Whizbang ID"}]}

我只是害怕JSON解析器如何处理它。我会找回HashMaps列表吗?

同样,我真正的问题是将LinkedHashMap数据结构从GSP发送到Controller的最佳方法是什么,以便我可以保留顺序,但在解析数据时只做最少的工作? I我假设这是JSON,但我很高兴被告知,“为什么不只是......”

2 个答案:

答案 0 :(得分:2)

我认为问题是Java / Groovy集合的性质与JSON的简单“它是列表或它是地图”的本质不匹配。没有进入自定义解析,我建议你改变你发送的东西。而不是试图强迫LinkedHashMap的Groovy概念进入Javascriptland,也许坚持Javascript理解的习惯用法,例如地图列表。

在代码中,而不是:

def tableFields = [firstName: "First Name", lastName: "Surname", unique_id: "Your Whizbang ID"]

怎么样:

List tableFields = [
    [ name: 'firstName', label: 'First Name' ],
    [ name: 'lastName', label: 'Surname' ],
    [ name: 'unique_id', label: 'Your Whizbang ID' ],
]

这会让你转向JSON,它会维护你需要的数据(我认为),同时给予JSON它理解的东西(列表):

<input type="hidden" name="columns" id="columns" value="[
    { "name": "firstName", "label": "First Name" },
    { "name": "lastName", "label": "Surname" },
    { "name": "unique_id", "label": "Your Whizbang ID" }
]" />

无论处理什么,这将是一个稍微深一点的迭代器,但这是从好的收藏品到更简单的类型的价格......

答案 1 :(得分:0)

我现在正在做的是传递当前的JSON对象和我可以迭代的列表。在GSP模板中,这看起来像:

<g:hiddenField name="columns" value="${colJson}"/>
<g:hiddenField name="columnOrder" value="${columns.collect{it.key}}"/>

其中columns是LinkedHashMap。

然后,在获得这些参数的控制器中,我这样做:

def columnTitles = params.columnOrder.tokenize(",[] ")
def unorderedColumns = JSON.parse(params.columns)
def columns = columnTitles.collectEntries{ [(it): unorderedColumns[it]] }

不优雅,但确实有效,而且需要的重构比Joe Rinehart的建议要少一些。