The ?js_string doesn’t work quite right for JSON since it ends up escaping single-quotes. This isn’t quite right for JSON since it only allows Strings to be specified using double-quotes. Therefore, in order to properly escape Strings in FreeMarker for JSON, you need to write a custom method and add it to the model. Here’s my code for a JSON escaper method:
public class JSONEscape implements TemplateMethodModelEx {
@Override
public Object exec(List arguments) throws TemplateModelException {
if (arguments.size() != 1) {
throw new TemplateModelException("jsonescape takes a single parameter");
}
return JSONBuilder.escape(arguments.get(0).toString());
}
}
The JSONBuilder is a class that I wrote for creating JSON using the builder pattern. However, the escape method looks like this:
public static String escape(String str) {
StringBuilder build = new StringBuilder();
escape(str, build);
return build.toString();
}
public static void escape(String str, StringBuilder build) {
char[] ca = str.toCharArray();
for (char c : ca) {
switch (c) {
case '"':
case '\\':
build.append('\\');
build.append(c);
break;
case '\r':
build.append("\\r");
break;
case '\n':
build.append("\\n");
break;
case '\t':
build.append("\\t");
break;
case '\b':
build.append("\\b");
break;
case '\f':
build.append("\\f");
break;
case '/':
build.append("\\/");
break;
default:
if (c < = 0x1F) {
unicodeEscape(c, build);
} else {
build.append(c);
}
}
}
}
public static void unicodeEscape(int ch, StringBuilder build) {
build.append('\\');
build.append('u');
build.append(HEX_CHARS[ch >>> 12]);
build.append(HEX_CHARS[(ch >>> 8) & 0xf]);
build.append(HEX_CHARS[(ch >>> 4) & 0xf]);
build.append(HEX_CHARS[ch & 0xf]);
}
Then I add this method to my model and render the template:
model.put("jsonescape", new JSONEscape());
My templates look like this:
{
"foo": "${jsonescape(some.value.here)}"
}