JSON array evaluating to StringModel in FreeMarker
Options
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
10-31-2014 05:57 AM
Hello,
I have a web script in Share that calls a repo web script which returns some JSON. I want to use this JSON in a FreeMarker template to build up a page but I'm having some problems. The repo side seems to be working absolutely fine. Here is an example of the JSON it returns:
This is my Share side Java controller which calls the repo web script and puts the results into the model.
This is the FTL:
And the error I'm getting is:
I have remotely debugged the application and can see that the JSONArray I create from the response in the controller is correctly formed. I have attached a screenshot showing this.
I'm sure I'm doing something simple wrong but I can't figure it out.
I have a web script in Share that calls a repo web script which returns some JSON. I want to use this JSON in a FreeMarker template to build up a page but I'm having some problems. The repo side seems to be working absolutely fine. Here is an example of the JSON it returns:
[{"Name":"Matrix - Connectivity problems","Problem":"Terminal won't connect\nConnection error"}]
This is my Share side Java controller which calls the repo web script and puts the results into the model.
package com.x.y.z;import java.util.HashMap;import java.util.Map;import org.alfresco.error.AlfrescoRuntimeException;import org.alfresco.util.ParameterCheck;import org.apache.commons.lang3.StringUtils;import org.json.JSONArray;import org.json.JSONException;import org.springframework.extensions.surf.RequestContext;import org.springframework.extensions.surf.ServletUtil;import org.springframework.extensions.surf.exception.ConnectorServiceException;import org.springframework.extensions.surf.support.ThreadLocalRequestContext;import org.springframework.extensions.webscripts.Cache;import org.springframework.extensions.webscripts.DeclarativeWebScript;import org.springframework.extensions.webscripts.Status;import org.springframework.extensions.webscripts.WebScriptException;import org.springframework.extensions.webscripts.WebScriptRequest;import org.springframework.extensions.webscripts.WebScriptSession;import org.springframework.extensions.webscripts.connector.Connector;import org.springframework.extensions.webscripts.connector.Response;public class ShareWebScript extends DeclarativeWebScript { @Override protected Map<String,Object> executeImpl(WebScriptRequest req, Status status) { return executeImpl(req, status, null); } @Override protected Map<String, Object> executeImpl(WebScriptRequest req, Status status, Cache cache) { String query = getParameterAsString(req, "query"); Map<String, Object> model = new HashMap<>(); try { final RequestContext rc = ThreadLocalRequestContext.getRequestContext(); final String userId = rc.getUserId(); final Connector conn = rc.getServiceRegistry().getConnectorService().getConnector("alfresco", userId, ServletUtil.getSession()); final String url = "/repo/search?query=" + query; final Response response = conn.call(url); if (response.getStatus().getCode() == Status.STATUS_OK) { JSONArray resultsResponse = new JSONArray(response.getResponse()); model.put("results", resultsResponse); } } catch (final ConnectorServiceException e) { throw new AlfrescoRuntimeException("Failed to connect repository", e); } catch (final JSONException e) { throw new AlfrescoRuntimeException("Failed to parse JSON string", e); } return model; } private String getParameterAsString(final WebScriptRequest req, final String paramName) { ParameterCheck.mandatory("req", req); ParameterCheck.mandatoryString("paramName", paramName); String paramVal = StringUtils.trimToNull(req.getParameter(paramName)); if (StringUtils.isBlank(paramVal)) { throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Missing " + paramName + " parameter"); } return paramVal; }}
This is the FTL:
<#escape x as jsonUtils.encodeJSONString(x)> { "Results" : [ <#list results as result> { "Title" : "${result.Name}", "Problem : "${result.Problem}" }, </#list> ] }</#escape>
And the error I'm getting is:
freemarker.template.TemplateException: Expected collection or sequence. results evaluated instead to freemarker.ext.beans.StringModel on line 4
I have remotely debugged the application and can see that the JSONArray I create from the response in the controller is correctly formed. I have attached a screenshot showing this.
I'm sure I'm doing something simple wrong but I can't figure it out.
Labels:
- Labels:
-
Archive
6 REPLIES 6
Options
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
10-31-2014 08:52 AM
Hello,
the problem with this is that JSONArray is by default not a supported data type to put into the model for the template processor. You should use proper array, List or Collection values when you populate the model in Java. Any unsupported data type will result in the template processor to convert the object into StringModel via its toString() operation.
Regards
Axel
the problem with this is that JSONArray is by default not a supported data type to put into the model for the template processor. You should use proper array, List or Collection values when you populate the model in Java. Any unsupported data type will result in the template processor to convert the object into StringModel via its toString() operation.
Regards
Axel
Options
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
10-31-2014 10:41 AM
Brilliant! Thanks so much that fixed my problem. My working code is listed below for others who have the same problem in the future.
And this is the FTL.
Thanks again, much appreciated.
package com.x.y.z;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;import org.alfresco.error.AlfrescoRuntimeException;import org.alfresco.util.ParameterCheck;import org.apache.commons.lang3.StringUtils;import org.json.JSONArray;import org.json.JSONException;import org.json.JSONObject;import org.springframework.extensions.surf.RequestContext;import org.springframework.extensions.surf.ServletUtil;import org.springframework.extensions.surf.exception.ConnectorServiceException;import org.springframework.extensions.surf.support.ThreadLocalRequestContext;import org.springframework.extensions.webscripts.Cache;import org.springframework.extensions.webscripts.DeclarativeWebScript;import org.springframework.extensions.webscripts.Status;import org.springframework.extensions.webscripts.WebScriptException;import org.springframework.extensions.webscripts.WebScriptRequest;import org.springframework.extensions.webscripts.WebScriptSession;import org.springframework.extensions.webscripts.connector.Connector;import org.springframework.extensions.webscripts.connector.Response;public class ShareWebScript extends DeclarativeWebScript { @Override protected Map<String,Object> executeImpl(WebScriptRequest req, Status status) { return executeImpl(req, status, null); } @Override protected Map<String, Object> executeImpl(WebScriptRequest req, Status status, Cache cache) { Map<String, Object> model = new HashMap<>(); List<Map<String, String>> results = new ArrayList<>(); try { final RequestContext rc = ThreadLocalRequestContext.getRequestContext(); final String userId = rc.getUserId(); final Connector conn = rc.getServiceRegistry().getConnectorService().getConnector("alfresco", userId, ServletUtil.getSession()); final String url = "/mySearch?query=" + query; final Response response = conn.call(url); if (response.getStatus().getCode() == Status.STATUS_OK) { JSONArray resultsResponse = new JSONArray(response.getResponse()); for (int i = 0; i < resultsResponse.length(); i++) { JSONObject result = resultsResponse.getJSONObject(i); Map<String, String> resultObject = new HashMap<>(); String title = result.getString("Name"); resultObject.put("ShareTitle", title); String problem = result.getString("Problem"); resultObject.put("ShareProblem", problem); results.add(resultObject); } model.put("results", results); } } catch (final ConnectorServiceException e) { throw new AlfrescoRuntimeException("Failed to connect repository", e); } catch (final JSONException e) { throw new AlfrescoRuntimeException("Failed to parse JSON string", e); } return model; } }}
And this is the FTL.
<#escape x as jsonUtils.encodeJSONString(x)> { "Results" : [ <#list results as result> { "Title" : "${result.ShareTitle}", "Problem : "${result.ShareProblem}" }, </#list> ] }</#escape>
Thanks again, much appreciated.
Options
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
05-19-2016 12:18 PM
I have a very similar problem, with the same error.
But in my case I am trying to use a javascript-based webscript.
To take the same example as Stuart Layland, but converted to javascript (The FTL is the same.):
<javascript>
var connector = remote.connect("alfresco-webscripts");
var webscriptUrl = "/mySearch?query=" + args.query;
var data = connector.get(webscriptUrl);
logger.log(data);
model.results = eval(data);
</javascript>
But I get the error: freemarker.template.TemplateException: Expected collection or sequence. results evaluated instead to freemarker.ext.beans.StringModel on line 4
Is it not possible to directly pass the eval'd object to the template model?
Do I really need to create a Java-based webscript for this?
But in my case I am trying to use a javascript-based webscript.
To take the same example as Stuart Layland, but converted to javascript (The FTL is the same.):
<javascript>
var connector = remote.connect("alfresco-webscripts");
var webscriptUrl = "/mySearch?query=" + args.query;
var data = connector.get(webscriptUrl);
logger.log(data);
model.results = eval(data);
</javascript>
But I get the error: freemarker.template.TemplateException: Expected collection or sequence. results evaluated instead to freemarker.ext.beans.StringModel on line 4
Is it not possible to directly pass the eval'd object to the template model?
Do I really need to create a Java-based webscript for this?
Options
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
05-20-2016 03:38 AM
Apart from the fact that you should avoid eval() altogether and instead use something like JSON.parse() to avoid code injection issues, you should be able to pass native JS objects / arrays into the model. The default integration of the Rhino JavaScript engine into web scripts should ensure that data put into model is converted from JS to Java (and if necessary + supported to specific FTL types).
Now since I don't know about the expected JSON structure that you parse I can't know if some fluke error in the JSON can result in the eval() to just return a String instead of an array you expected…
Now since I don't know about the expected JSON structure that you parse I can't know if some fluke error in the JSON can result in the eval() to just return a String instead of an array you expected…
Options
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
05-20-2016 10:15 AM
Dear Axel, Thank you for your reply.
In the meantime I tried to do in javascript the operation of "manually" converting the model to javascript objects, like this:
<javascript>
var connector = remote.connect("alfresco-webscripts");
var webscriptUrl = "/mySearch?query=" + args.query;
var response = connector.get(webscriptUrl);
var data = eval(response);
logger.log(data);
var results = [];
for (var i=0,n=data.length; i<n; i++) {
var item = data;
results.push({"id":item.id, "name":item.name, "title":item.title});
}
model.results = results;
</javascript>
But it was not working –> the results were an empty array, because data.length was undefined.
Later I dropped the eval call and replaced it with jsonUtils.toObject(data)
But then I started getting errors like: org.mozilla.javascript.EcmaError: TypeError: Cannot find function length.
While debugging this, something not at all helpful (and one of the most annoying) was the fact that I cannot perform operations like:
<javascript>
var a = jsonUtils.toObject('{"a":123}');
logger.log(a); // ==> org.mozilla.javascript.EcmaError: TypeError: Cannot find default value for object.
logger.log(typeof a); // ==> "object" What kind of object?? What fields and methods does it have??
</javascript>
In the end I removed the "manual" re-assignment, and now it's working with just this:
<javascript>
var connector = remote.connect("alfresco-webscripts");
var webscriptUrl = "/mySearch?query=" + args.query;
var response = connector.get(webscriptUrl);
var data = jsonUtils.toObject(response);
model.results = data.results;
</javascript>
Note that in order to make it work I had to also change the FTL for my AlfrescoRepo data-webscript, which was returning a JSON Array:
<javascript>
[
{"id":"1","name":"John","Title":"Johnny"},
{"id":"2","name":"Jane","Title":"Jenny"},
]
</javascript>
I was getting the following error from jsonUtils.toObject(response): org.json.simple.JSONArray cannot be cast to org.json.simple.JSONObject
I had to change it to return a JSON Object, like this:
<javascript>
{"results":[
{"id":"1","name":"John","Title":"Johnny"},
{"id":"2","name":"Jane","Title":"Jenny"},
]}
</javascript>
In the meantime I tried to do in javascript the operation of "manually" converting the model to javascript objects, like this:
<javascript>
var connector = remote.connect("alfresco-webscripts");
var webscriptUrl = "/mySearch?query=" + args.query;
var response = connector.get(webscriptUrl);
var data = eval(response);
logger.log(data);
var results = [];
for (var i=0,n=data.length; i<n; i++) {
var item = data;
results.push({"id":item.id, "name":item.name, "title":item.title});
}
model.results = results;
</javascript>
But it was not working –> the results were an empty array, because data.length was undefined.
Later I dropped the eval call and replaced it with jsonUtils.toObject(data)
But then I started getting errors like: org.mozilla.javascript.EcmaError: TypeError: Cannot find function length.
While debugging this, something not at all helpful (and one of the most annoying) was the fact that I cannot perform operations like:
<javascript>
var a = jsonUtils.toObject('{"a":123}');
logger.log(a); // ==> org.mozilla.javascript.EcmaError: TypeError: Cannot find default value for object.
logger.log(typeof a); // ==> "object" What kind of object?? What fields and methods does it have??
</javascript>
In the end I removed the "manual" re-assignment, and now it's working with just this:
<javascript>
var connector = remote.connect("alfresco-webscripts");
var webscriptUrl = "/mySearch?query=" + args.query;
var response = connector.get(webscriptUrl);
var data = jsonUtils.toObject(response);
model.results = data.results;
</javascript>
Note that in order to make it work I had to also change the FTL for my AlfrescoRepo data-webscript, which was returning a JSON Array:
<javascript>
[
{"id":"1","name":"John","Title":"Johnny"},
{"id":"2","name":"Jane","Title":"Jenny"},
]
</javascript>
I was getting the following error from jsonUtils.toObject(response): org.json.simple.JSONArray cannot be cast to org.json.simple.JSONObject
I had to change it to return a JSON Object, like this:
<javascript>
{"results":[
{"id":"1","name":"John","Title":"Johnny"},
{"id":"2","name":"Jane","Title":"Jenny"},
]}
</javascript>
Options
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
07-21-2016 07:09 AM