Search code examples
jsongrailsgsp

Grails LinkedHashMap to JSON Preserve Order on RoundTrip


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..."


Solution

  • 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.