We have a server-side rendered HTML page which includes an external JavaScript file. To trigger the code in that file, we want to call a function and pass it some dynamic data (in the form of JSON):
<script src="/static/foo/bar.js"></script>
<script>
  foo.bar.init({biz: 42, qux: "quux"});
</script>
We're rendering this from a Nunjucks template, and passing the JSON object as a value data in the context. This can contain arbitrary data, including user-provided content.
This is safe but does not work, because <>& are being escaped (thanks to Nunjucks autoescaping):
foo.bar.init({{ data | dump }});
This works, but is not safe, because a string in the JSON might contain the text </script>:
foo.bar.init({{ data | dump | safe }});
How to convince Nunjucks to render this JSON so it can be interpreted safely and correctly? It sounds like it should be a solved problem, but I can't find a premade solution anywhere.
Doing this for now:
  /**
   * Returns a JSON stringified version of the value, safe for inclusion in an
   * inline <script> tag. The optional argument 'spaces' can be used for
   * pretty-printing.
   *
   * Output is NOT safe for inclusion in HTML! If that's what you need, use the
   * built-in 'dump' filter instead.
   */
  env.addFilter('json', function (value, spaces) {
    if (value instanceof nunjucks.runtime.SafeString) {
      value = value.toString()
    }
    const jsonString = JSON.stringify(value, null, spaces).replace(/</g, '\\u003c')
    return nunjucks.runtime.markSafe(jsonString)
  })
Usage:
<script src="/static/foo/bar.js"></script>
<script>
  foo.bar.init({{ data | json }});
</script>
Better solutions still welcome.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With