Question

tl;dr

I need to send the data from a LinkedHashMap in a GSP template to a Controller and preserve the order of the elements.

I'm assuming a structured data format like JSON is the ideal way to do this, but Grails' JSON converter doesn't create an ordered JSON object from a LinkedHashMap.

What is the best way to send a LinkedHashMap data structure from a GSP to a Controller so that I can preserve order, but do minimal work in parsing the data?

Long version

I'm developing a taglib to render search results in a table.

In the taglib, I construct a LinkedHashMap that specifies the data columns and the labels that the user wants to show for the column names. For example:

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

That map gets sent to a view, which will then send it back to a controller to retrieve the search results from the database. I need to preserve the order of the elements (hence the use of a LinkedHashMap).

My first thought was to turn the LinkedHashMap into a JSON string, and then send it to the controller via a hidden form element. So,

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

However, that creates a JSON Object like this in the HTML. I'm putting this in a hidden field's value attribute.

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

Here's the JSON object by itself.

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

You can see that the JSON string's properties are in the same order as the LinkedHashMap in the JSON string. However, JSON Objects aren't really supposed to the preserve order of their properties. Thus, when my controller receives the columns parameter, and I use the JSON.parse() method on it, it creates a plain ol' unordered HashMap instead of a LinkedHashMap. As a result, the columns in my search results display in the wrong order when I render them into an HTML table.

At least one fellow has had a similar problem. Adding as LinkedHashMap after running JSON.parse() doesn't cut it, since the .parse() method screws up the order from the get go.

Daniel Woods, in his response to the above post, noted:

If it's a matter of the grails data binder not working for you, you should be able to override the implicit property setter to cast the object to your favorite Map implementation.

I assume that he's saying I could write my own parser, which would honor the order of the JSON elements (even though it technically shouldn't). I imagine I could also write my own converter so that the resulting JSON element would be something like:

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

I'm just about terrified of how the JSON parser would handle that, though. Would I get back a list of HashMaps?

Again, my real question is What is the best way to send a LinkedHashMap data structure from a GSP to a Controller so that I can preserve order, but do minimal work in parsing the data? I'm assuming that's JSON, but I'm more than happy to be told, "Why not just..."

Was it helpful?

Solution 2

What I'm doing for now is passing both the current JSON object and a list that I can iterate through. In the GSP template, this looks like:

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

where columns is the LinkedHashMap.

Then, in the controller that gets those params, I do this:

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

Not elegant, but it does work, and it requires a bit less refactoring than Joe Rinehart's suggestion.

OTHER TIPS

I think the issue is a mismatch between the nature of Java/Groovy collections and the simple "it's a list or it's a map" nature of JSON. Without getting into custom parsing, I'd suggest shifting what you're sending a bit. Instead of trying to force Groovy notions of a LinkedHashMap into Javascriptland, maybe stick to an idiom Javascript understands, such as a list of maps.

In code, instead of:

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

how about:

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

This shifts you to JSON that'd maintain the data (I think) you need while giving JSON something it understands is ordered (a list):

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

Whatever handles this will be a slightly deeper iterator, but that's the price of going from a land of good collections to simpler types...

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top