Safely Using JSON with html inside of the JSON in Django Templates

11,346

Safely insert the JSON as a string, and then call JSON.parse on it

Use escapejs instead of safe. It is designed for outputting to JavaScript.

var the_json = '{{my_json|escapejs}}';

To get a JavaScript object you then need to call JSON.parse on that string. This is always preferable than dumping a JSON-encoding into your script and evaluating it directly, for security reasons.

A useful filter to get python objects directly to the client that I use is this:

@register.filter
def to_js(value):
    """
    To use a python variable in JS, we call json.dumps to serialize as JSON server-side and reconstruct using
    JSON.parse. The serialized string must be escaped appropriately before dumping into the client-side code.
    """
    # separators is passed to remove whitespace in output
    return mark_safe('JSON.parse("%s")' % escapejs(json.dumps(value, separators=(',', ':'))))

And use it like:

var Settings = {{ js_settings|to_js }};
Share:
11,346
speedplane
Author by

speedplane

Docket Alarm indexes hundreds of millions of lawsuits, analyzes what happens in them, and predicts what will happen in future cases. We package this intelligence up into a SaaS service and sell it to lawyers at top law firms. To analyze the law, we deal with lots of interesting NLP and unstructured data problems, and to tackle them, we use some of the latest machine-learning tools. We're always looking for skilled developers with a passion in the intersection of law and technology. If that's you, please get in touch.

Updated on June 18, 2022

Comments

  • speedplane
    speedplane almost 2 years

    How do you safely render JSON data in a django webapp?

    On the server in django I generate JSON data and then render that JSON data in a django template. The JSON occasionally contains snippets of html. Most of the time, that's fine, however if the </script> tag is inside the JSON data when it is rendered, it destroys the surrounding javascript.

    For example...

    On the server, in python I'll have this:

    template_data = {
        'my_json' : '[{"my_snippet": "<b>Happy HTML</b>"}]'
    }
    # pass the template data to the django template
    return render_to_response('my_template.html', template_data, context_instance = c)
    

    And then in the template:

    <script type="text/javascript">
    var the_json = {{my_json|safe}};
    </script>
    ... some html ...
    

    The resulting html works fine and looks like this:

    <script type="text/javascript">
    var the_json = [{"my_snippet": "<b>Happy HTML</b>"}];
    </script>
    ... some html ...
    

    However, you run into problems when, on the server, the JSON looks like this:

    template_data = {
        'my_json' : '[{"my_snippet": "Bad HTML</script>"}]'
    }
    return render_to_response('my_template.html', template_data, context_instance = c)
    

    Now, when it's rendered, you'll get:

    <script type="text/javascript">
    var the_json = [{"my_snippet": "Bad HTML</script>"}];
    </script>
    ... some html ...
    

    The closing script tag within the JSON code is treated as closing the entire script block. All of your javascript will then break.

    One possible solution is to check for </script> when passing the template data to the template, but I feel like there is a better way.

  • speedplane
    speedplane over 11 years
    If you do that then the_json becomes a string, not a javascript json object.
  • DanielB
    DanielB over 11 years
    You could do var the_json = JSON.parse({{my_json|escapejs}})
  • speedplane
    speedplane over 11 years
    I think you need to quote it, like this: var the_json = JSON.parse('{{my_json|escapejs}}')
  • Flash
    Flash over 11 years
    The first line will simply get a string safely into your javascript. You then need to deserialize which is why I've created the filter. You should definitely use JSON.parse rather than hoping it parses as a legal object in any case.
  • xolox
    xolox over 8 years
    Surprisingly tricky to get this nested escaping right, I couldn't quite reason my way out of it even though I fully understood the problem. Huge thanks to Andrew for (nicely) documenting the proper way to do this!