<script th:inline="javascript" type="text/javascript">
//expose list data to javascript
var listObject = /*[[${listObject}]]*/ [];
</script>
the replacement text printed into the file is different than what Jackson library's ObjectMapper does.
With Thymeleaf in above example, listObject will be
{
"dataType":{
"$type":"DataType",
"$name":"STRING"
},
"friendlyName":"Customer Key"
}
If I print the object with ObjectMapper(which is also used with Spring #RequestBody/#ResponseBody), it will be
{
"dataType":"STRING",
"friendlyName":"Customer Key"
}
Is there a way I can force thymeleaf to be compatible with ObjectMapper.
I think this has to say something about Jackson and JSON inlining in thymeleaf.
To summarize, the possibility to switch to custom TextInliners is considered for
3.0 thymeleaf milestone.
So, currently there is no "clean" way to switch to Jackson json serialization.
What you can do however, is sneak your own TextInliner. That is:
Create a class org.thymeleaf.standard.inliner.StandardJavaScriptTextInliner.
Implement your own version of formatEvaluationResult(Object) method,
where you can call the Jackson ObjectMapper .
Put this new StandardJavaScriptTextInliner class in a proper place, so that it is loaded before the original class (f.e. in tomcat put it in classes dir under correct package structure).
Another option:
when you set listObject in the thymeleaf context, set it to the string that is obtained by converting listObject to a JSON string using Jackson
then use JS eval() or the better method - JSON.parse to convert the string into a JS object.
Related
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?
In a Spring Boot project, I have a list of users, in Java List<User>
I pass it from the Controller to the template, I am able to loop through this list using a HTML list ul but I am not able to do it in JavaScript:
<script layout:fragment="script" th:inline="javascript">
/*<![CDATA[*/
var users = /*[[${users}]]*/ [];
for (var i = 0; i < users.length; i++) {
console.log(i); // Obviously here I would like to access the User properties
}
/*]]>*/
</script>
I get this error:
Error during execution of processor
'org.thymeleaf.standard.processor.text.StandardTextInliningTextProcessor'
How do we loop through lists and access Java object properties in JavaScript with Thymeleaf?
Thanks.
EDIT: what I have discovered so far
My User class is a JPA entity with a Country property (Country is another JPA entity):
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "country_id")
private Country country;
If I pass a list of users without the country fetched the JavaScript works.
If I pass a list of countries and loop through (to test the Country class by itself), it works too.
If I pass a list of users where I set the country myself (not fetched from database) with the same values as the database contains, it works
If I retrieve the country from the database, then set it to the user, it fails.
So it seems like the problem is because the Country entity is created/mapped to the User a certain way by Spring Data/Hibernate that Thymeleaf can not deserialize it...
JavaScript natural templates
Have you got Jackson library in your classpath?
If you have it, It should work.
If you take a look the reference documentation it works as you are doing it:
An important thing to note regarding JavaScript inlining is that this
expression evaluation is intelligent and not limited to Strings.
Thymeleaf will correctly write in JavaScript syntax the following
kinds of objects:
Strings ...
That ${session.user} expression will evaluate to a User object, and Thymeleaf will correctly convert it to Javascript syntax
At least, other way If you want, is configure how the Serialization is done with an instance of StandardDialect:
The way this JavaScript serialization is done is by means of an
implementation of the
org.thymeleaf.standard.serializer.IStandardJavaScriptSerializer
interface, which can be configured at the instance of the
StandardDialect being used at the template engine.
As I've said before, you should have Jackson Library in your classpath (although the reference says it will have similar results I would add it):
The default implementation of this JS serialization mechanism will
look for the Jackson library in the classpath and, if present, will
use it. If not, it will apply a built-in serialization mechanism that
covers the needs of most scenarios and produces similar results (but
is less flexible).
Finally, try to log users content (console.log(users)) to see what is doing the serialization.
Java mapping
Other easier way is If you want to iterate a list of java objects yous could map it to a Json in Java (using a library like Jackson or Gson) and then add it as parameter to the thymeleaf template.
ObjectMapper mapper = new ObjectMapper();
String users = mapper.writeValueAsString(users);
model.addAttribute("users", users);
The result of the parameter will be in a JSON format.
I have problem with send data between my two apps. I serialize data to JSON in C# using this code:
public static string SerializeToJson<T>(this T obj)
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(obj.GetType());
MemoryStream ms = new MemoryStream();
serializer.WriteObject(ms, obj);
byte[] array = ms.ToArray();
return Encoding.UTF8.GetString(array, 0, array.Length);
}
and then i send this using socket communication to my second application which is implemented in TypeScript. I deserialize it using:
JSON.parse
function and it works fine, but if in data is special characters for example 8211 'ā' it throw exception
SyntaxError: Unexpected token in JSON at position 907
Maybe it is problem with different encoding with serialize and deserialize, but I don't know which encoding is used in JSON.parse.
Anyone can help me?
An alternative is to use Newtonsoft Json.Net (available from nuget).
It's easy to use and very powerfull.
public static string SerializeToJson<T>(this T obj)
{
returnĀ JsonConvert.SerializeObject(obj);
}
And you can even add some formating or what you want.
I resolve this problem using convert to base64 my string and then decode it in my second application.
The following code worked for me. The following solution also ensures the type of underlying objects. To Convert C# object to typescript object.
Convert your object to json format using any of the JSON libraries. (newtonsoft is most suggested one)
string output = JsonConvert.SerializeObject(product); // product =new Product(); <= Product is custom class. substitute your class here
Pass this string to your application. (ActionResult or ajax call)
Now in the javascript access the value using Model (Razor or ajax result)
YourTSClass.ProcessResults('#Model') // using razor in js
or
.done(response => {ProcessResults(response)}) // using ajax success result
You need not apply JSON.Parse this way.
In the typescript you can get the result by declaring the function like this.
public static ProcessResults(result: Product){...}
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.
Ok so I have a template in Play! that receives a List as a parameter:
#(actions : List[RecipientAction])
RecipientAction is just a regular case class with a couple of fields. Within the template, I have a <script> tag where I want to use D3 to make a line chart. Inside the script I want to populate a JavaScript array with objects that contain the properties stored in RecipientAction in order to use them for my line chart later. I currently have this code:
testArray2=[];
for(var i=0; i < #actions.length;i++){
testArray2[i]= {};
testArray2[i].eventAt= #actions(i).eventAt.toString();
testArray2[i].action= #actions(i).action.id;
}
When i run it, i get the error "not found: value i". This is because i is a client side variable while actions is a server side variable, so scala cannot find i. What would be the best way to work around this and successfully populate the array?
You need to create a JSON serializer for your RecipientAction, then you'll just be able to print the list as JSON in the template. Say it looks something like this..
import play.api.libs.json._
case class RecipientAction(id: Int, description: String)
object RecipientAction {
// Define a `Writes` for `RecipientAction`
implicit val writes: Writes[RecipientAction] = Json.writes[RecipientAction]
}
I used one of the JSON macros included with Play that will automatically create a Writes for a case class, since all we care about is printing the list.
Then in your template:
#(actions : List[RecipientAction])
#import play.api.libs.json.Json
<script type="text/javascript">
var testArray = #Html(Json.stringify(Json.toJson(actions)));
</script>
The definition of an implicit Writes is required so that Json.toJson knows how to convert the class to JSON. For more about Json serialization/deserialization see the documentation.