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: -
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();
I populated this map with all the category and skill information and then added it to my Model: -
model.addAttribute("skillsMap", skillsMap);
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');
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
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);```
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 = {"Languages_OD":[{"id":66,"name":"SQL"},{"id":67,"name":"PL/SQL"}], 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: -
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(/"/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! :)
Symbols """ 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?
Related
I am afraid I don’t understand what "serializing" means. If someone can explain I’d be grateful. I am serializing a Django model in Json as explained in the Django Docs:
data = serializers.serialize("json", MyModel.objects.all())
In my HTML/JS I access the data as recommended:
{{ data|json_script:"data" }}
var myData = JSON.parse([document.getElementById('data').textContent]);
But instead of being an Object myData is a string. So I guess somehow I serialize twice or something.
I found a solution, JSON.parse works as expected on my data now:
data = json.loads(serializers.serialize("json", CoursePage.objects.child_of(self).live().public()))
But I guess I still don’t understand the meaning of "serializing" properly. The Python docs say about json.loads(s): "Deserialize s (a str instance containing a JSON document). Why do I have to deserialize before JSON.parse works? The description for JSON.parse states: "The JSON.parse() method parses a JSON string"? Which I thought Djangos serializer would gave me in the first place. I am confused.
The json_script filter is for Python objects. But serialization is already the conversion of Python objects into JSON. So effectively you're converting it twice.
In your case I wouldn't bother with that filter. Just remove the json.loads and output the data directly where you need it:
var myData = JSON.parse("{{ data|safe }}");
I'm programming in oTree (which is a Django based environment for social experiments) and I have the following problem. I defined some lists in Python and I'd like to import them and use them in an HTML template. If I print them in HTML I manage to see them without any problem, however, once I need to use them in Javascript, the program fails to read them and the single quotes of the elements of the list are converted in '.
The list is imported like this var filtered_elements = {{ array }};.
I think the problem is exactly here, as JS cannot work with them. Do you have any suggestion on how to do that? I considered using JSON, but since I'm quite new to programming, I cannot understand if it's just a waste of time or there is a simpler way out.
Thanks for your answers!
It sounds like your data is already JSON, otherwise you would be getting single quotes and u prefixes. So the only issue is Django autoescaping; you can disable it with the safe filter:
var filtered_elements = {{ array|safe }};
Your data should be JSON, instead of putting the Python list into the contact directly, put "array": json.dumps(array) in the context dictionary.
The JSON string doesn't need HTML escaping inside a tag, but it does need JS escaping! Otherwise some string may include something like </script><script>absolutely anything goes here... to run arbitrary JavaScript, if the JSON contains user data.
So use |escapejs:
var filtered_elements = {{ array|escapejs}};
I'm using Play Framework and I have a .java Controller file in which I obtain an array of strings. I want to pass this Java array into an html file that will use Javascript in order to plot the data using Flot Charts. This data "transfer" is done in the render. It is something like this:
String[] array = new String[list.size()];
int i = 0;
for (Sample sample : list) {
array[i++] = sample.getContent();
}
render(array);
But then when I'm unable to call this variable in the .html file inside the views folder. If I use ${array}, Firebug tells me that it does not recognize it as a valid JS String array. I've read that Rhino or Nashorn could do the trick, but I do not know if they are the best and simplest option. Any ideas? Thanks!
I'm not familiar with Play Framework but I'm doing similar stuff using SparkJava in both java and javascript (using Nashorn).
I would suggest to use Boon library to generate json: https://github.com/boonproject/boon.
Here's a small Nashorn snippet to get you up to speed, easily adaptable to java:
// 1st we create a factory to serialize json out
var jso = new org.boon.json.JsonSerializerFactory().create();
// 2nd we directly use boon on array variable. Boon supports out of the box many pure java objects
jso.serialize(o);
In your specific case, you'll need to configure Play output for that particular render as application/json and possibly use render(jso.serialize(o)); in place of the small snippet I gave.
I have a Rails/backbone app I'm working on. I'd like to convert one of the attributes coming from Rails to a javascript array in backbone.
Rails is delivering the attribute as stringified JSON (I think)
$(function () {
App.initialize({ projectsJson: [{"skill":"[\"Beginner\",\"Advanced\"]"}] });
});
Then trying to convert it in backbone:
App.Models.Project = Backbone.Model.extend({
initialize: function() {
this.set({
skill: JSON.parse(this.get('skill'))
});
}
I get this error: Uncaught SyntaxError: Unexpected token B
I've tried inspecting the attribute in devtools:
var test = this.get('skill');
Which shows: "["Beginner","Advanced"]"
and:
var test = JSON.parse(this.get('skill'));
Which shows an array object with 2 elements.
If I make a new attribute:
this.set({
skillTEST: JSON.parse(this.get('skill'))
});
I can then use this new attribute and it works fine as an array. I'm very confused. Why can't I change the original attribute?
Is there a better way to do this? All I really want is that string coming from rails to be an array in backbone.
UPDATE:
I took a new approach based on meagar's answer. I decided to just not pass the array around as JSON.
the code:
JSON.stringify(new Array("Beginner","Advanced"));
produces: "["Beginner","Advanced"]"
backbones collection.create method is sending a POST request with:
{"skill":"[\"Beginner\",\"Advanced\"]"}
And I could not figure out how to fix that so I stopped using JSON.stringify.
The string "\"Beginner\",\"Advanced\"]" is not valid JSON, so no, it will not parse.
You either need to stop using JSON (it's really not apparent why you are here) or use a correctly formatted JSON string.
In the first case, you would simply pass around JavaScript arrays, and specify your configuration with an array literal:
App.initialize({ projects: [{skill: ['Beginner', 'Advanced']}] });
In the second case, you need to produce a valid JSON string in order to parse it:
App.initialize({ projectsJson: [{skill: '["Beginner","Advanced"]'}] });
In either case, you can make things far easier on yourself by choosing the correct quotes so that you don't need to escape your internal quotes, and by not needlessly wrapping your object literal keys in quotes.
I used to pass the variables in terms of dictionary to HTML template in Django without any problem. Now I plan to add some javascript into the HTML file, and use the same way "{{ }}" to obtain the object from dictionary in javascript. All the objects in the dictionary are actually strings.
In Django, the code is:
dataDict = {}
dataDict["time"] = json.dumps(",".join(timeList))
return render_to_response("xxxx.html", dataDict,\
context_instance=RequestContext(request))
And in HTML page and Javscript, I just want use a string variable to receive it and then resolve the information I want, and our code is:
var test = {{ time }};
But it seems Django cannot pass the data to the string variable in Javascript. So I have two questions:
Is there some special type of variable to pass the data from the Django to Javascript. Does JSON be possible? Or is it a must to convert string to JSON string in Django first before passing to Javascript?
How to use the delivered string in Javascript? Apparently just use var xx = xxxx; doesn't work. We think we can pass data but it seems cannot be processed.
Does anybody have some idea about it?
Thanks!
Maybe you just need to double quote it?
var test = "{{time}}";
Let's assume that dataDict['time'] = "1:30 PM, 2:30 PM". When your template is rendered it creates this text (verify it yourself w/ view source):
var test = 1:30 PM, 2:30 PM;
As you can see, this isn't valid JavaScript. When you double quote it it becomes this:
var test = "1:30 PM, 2:30 PM";
Similarly, you'll want to double quote your DOM elements with interpolated attributes, e.g., ... The Django builtin template filter docs have numerous examples of this.
It's important to keep in mind the difference between template evaluation / rendering time in your Python environment and JavaScript browser execution / evaluation time, especially when you try to pass data between the two.
Converting to JSON is recommended in order to prevent things such as spurious </script>-containing strings from causing issues with JavaScript. Assigning should be enough, since JSON strings look like JavaScript literals.