0

I am developing a Spring Boot MVC application that uses Thymeleaf templates on the front end.

I am trying to bind a HashMap from my MVC model to a JavaScript variable in one of my Thymeleaf templates. This is what has been done so far: -

  1. In my MVC controller class, I created a HashMap that represents user skills organised into categories. The Skill class is a data object containing name and id properties: -

    Map<String, List<Skill>> skillsMap = new HashMap();

  2. I populated this map with all the category and skill information and then added it to my Model: -

    model.addAttribute("skillsMap", skillsMap);

  3. On my Thymeleaf template in a script section, I am attempting to bind this HashMap to a variable. As a second step I then attempt to retrieve one of the lists from the map and assign to a second variable: -

    var skillsMapMap = [[${skillsMap}]];

    var adminList = skillsMapMap.get('Admin');

  4. When I debugged this, I could see that the HashMap was being read and an attempt was being made to bind it to my JavaScript variable: -

    var skillsMapMap = {Languages=[Python, SQL, PL/SQL, Java], Databases=[MySQL, Oracle, Mongo], Editors=[Word, TextPad, Notepad]};

This looked good at first glance and I could see that it contained all my data, but it was throwing the following error: -

Uncaught Syntax Error: invalid shorthand property initializer
  1. Having researched this, I realized that this error was caused because Java does not by default serialize the map in valid JSON format and so the attempted bind to a JavaScript variable failed. So, instead of just adding the HashMap straight to the Model as in step 2, I added some code to use Jackson to convert it into a JSON String first: -

    
    //Populate the map with all required data then....
    
    String objectMapper = null;
    try {
        objectMapper = new ObjectMapper().writeValueAsString(skillsMap);
    } catch (JsonProcessingException e) {
        e.printStackTrace();
    }
    model.addAttribute("skillsMap", objectMapper);```
    
  2. This time When I attempt to bind this to my JavaScript variable, the object looks like this when I debug in my browser: -

var skillsMapJson = {&quot;Languages_OD&quot;:[{&quot;id&quot;:66,&quot;name&quot;:&quot;SQL&quot;},{&quot;id&quot;:67,&quot;name&quot;:&quot;PL/SQL&quot;}], etc, etc};

The JSON now looks valid, but all the quotes are escaped and it now throws a different exception: -

```Uncaught SyntaxError: Unexpected token &```

I feel that if the JSON string contained actual quotes instead of " the Map would successfully bind to my variable. I would appreciate any advice as to how to deal with this. Many thanks for reading.

EDIT: Screenshot of error added below: -

JSON encoding issue in JavaScript variable

Jon H
  • 394
  • 3
  • 17
  • There has to be an easier way, but you can convert the " values in the string by decoding it: URLDecoder.decode(value, StandardCharsets.UTF_8.toString()); – Arlo Guthrie Nov 05 '19 at 15:35
  • Also, it's my understanding that Gson is much more flexible with complex objects. The article https://crunchify.com/in-java-how-to-convert-map-hashmap-to-jsonobject-4-different-ways/ shows many ways to convert a map, but doesn't cover complex maps. But it does show how to use Gson to generate json. ===> GsonBuilder gsonMapBuilder = new GsonBuilder(); Gson gsonObject = gsonMapBuilder.create(); String JSONObject = gsonObject.toJson(crunchifyMap); – Arlo Guthrie Nov 05 '19 at 15:41
  • @ArloGuthrie Many thanks for these suggestions. I will definitely try out that crunchify article. Regarding the URLDecoder suggestion, when I debug within the controller on the back end, I can see that the JSON String looks fine when it is first created and does not contain the " values. They seem to be added when the object goes 'over the wire'. So really I need to either prevent it happening at all or attempt to do the decoding on the front end - I'm just not sure how! Hugely appreciate your responses! :) – Jon H Nov 05 '19 at 16:23
  • Oh! Sorry, in javascript it's even easier. var decodedString = decodeURI(encodedString); – Arlo Guthrie Nov 05 '19 at 16:59
  • @ArloGuthrie Thank you again for this. I changed my code in the JavaScript to this: var skillsMapJson = decodeURI([[${skillsMap}]]); Frustratingly it is still not working and I am still seeing the 'unexpected token & error'. However, when I copy/paste the JSON string, including the " into this JavaScript decodeURI test site: https://www.w3schools.com/jsref/jsref_decodeuri.asp it works perfectly and decodes the " I feel I must be pretty close, but just missing something small. Really appreciate your guidance! Will keep trying! – Jon H Nov 05 '19 at 20:22
  • You are calling JSON.parse(data) on the javascript side, right? I would suggest adding more of your javascript code since this seems to be a javascript issue at this point. – Arlo Guthrie Nov 05 '19 at 21:30
  • @ArloGuthrie Thanks for the continued advice. The front end stuff is my weakest area, so I could very well be doing something wrong there! I am not calling JSON.parse(data). The very first line in my JavaScript code is var skillsMapJson = decodeURI([[${skillsMap}]]); where skillsMap is the HashMap added to the model by the controller. Later my script references this map to attempt to generate cascading dropdown boxes, but that is irrelevant for now because this very first line fails. – Jon H Nov 06 '19 at 10:23
  • When I debug the JavaScript code in my browser, I see a red line under the json string (I can clearly see lots of &quot in that string) and and when I hover over the red line, I see the 'unexpected token &' error. The fact that the json string is there at all shows that it has been successfully added to t e model and [[${skillsMap}]] is correctly referencing it - but the encoding is what's spoiling the party! I am now looking into my spring boot config to see if I can prevent this happening, perhaps by enforcing UTF-8 encoding? – Jon H Nov 06 '19 at 10:26
  • @ArloGuthrie Could you perhaps suggest another way of referencing the HashMap other than var skillsMapJson = decodeURI([[${skillsMap}]]);? Should I try JSON.parse(data), where data is the map? – Jon H Nov 06 '19 at 10:30
  • I have just added a screenshot showing the error. You can see that this occurs in the very first line of JavaScript. The code after that is still very much in draft format. I am trying to use that map to construct options for a second level dropdown box. – Jon H Nov 06 '19 at 10:47
  • @ArloGuthrie Hi again! Just to let you know that I posted a solution, albeit not a very elegant one. Many thanks for your help with this issue. – Jon H Nov 07 '19 at 11:26

2 Answers2

1

I did eventually get round this problem and so am offering a solution in case it helps anyone else. However, I feel that the solution is a bit 'hacky' and so would welcome any further answers that improve on this or offer a more elegant solution.

The problem was in the way I was trying to retrieve my map from the Model and assign to a JavaScript variable. Initially, I was trying this: -

var skillsMapRawString = [[${skillsMapJson}]];

The trouble is that this way of referencing skillsMapJson forces JavaScript to treat it as an Object and it cannot deal with the encoding issue described in the original post when attempting to deserialize it into JSON. The above line therefore threw the exception "unexpected token &".

By adding single quotes around the object reference, JavaScript is forced to treat skillsMapJson as a String and not an object: -

var skillsMapRawString = '[[${skillsMapJson}]]';

The above variable assignment now works successfully, but we still have the problem that it contains encoded quotes which prevent it being parsed to a JSON Object. This is where it feels like a bit of a hack, because I got round this by doing a global replace on the String: -

var skillsMapJSONString = skillsMapRawString.replace(/&quot;/g, "\"");

Having replaced the encoded sections that were causing problems before, the String can now be parsed as JSON: -

var skillsMapParsed = JSON.parse(skillsMapJSONString);

And finally, I have my map of lists successfully assigned to a JavaScript variable! :)

Jon H
  • 394
  • 3
  • 17
0

Symbols "&quot;" and similar are HTML escapes. So your info is HTML escaped. You can unescape it back by using class org.apache.commons.text.StringEscapeUtils from the library apache.commons/commons-text. The info found at: How to unescape HTML character entities in Java?

Michael Gantman
  • 7,315
  • 2
  • 19
  • 36
  • Thank you for this - I will read this article now. It would be good to have a better solution than the one I posted :) – Jon H Nov 07 '19 at 12:16