Serialize Django Data for Javascript
Serializing Django view context data for use in Javascript is something I’ve always thought should be extremely easy, but any time you have data that is more then a simple data type like a string or an integer, it can get complicated pretty quickly and can end up causing hard-to-solve bugs.
In most cases, if you have context data in your view like the following:
Then all you have to do to make it work in a template is to use the safe template filter and then it just works. Example:
But what if your data is more complex like the following example?
Using the safe
template filter no longer works because your booleans aren’t
converted to lowercase for use in Javascript as well as some of the other data
types like the QuerySet, the Python datetime object, and your HTML string.
You might try using something like the escapejs hoping
to find some easy template filter that just works. However, I’ll save you the
trouble; it doesn’t work and the escapejs filter is only meant for
use on a single variable and not a list of dicts. You also might try using
json.dumps()
with Django’s serialize() function, but as
I discovered this doesn’t work so well for most of my use cases because the
serialize()
function doesn’t serialize the model instances in a flattened
structure and serializes the data in a more nested structure. Example:
The Magic Solution
So if you’re wanting a Django template filter that just works, you’ll have to create your own. The following is the solution I came up with that just works and extends Django’s DjangoJSONEncoder encoder class which adds the functionality to serialize QuerySets.
With the previous to_json
template filter created, then using it in a template
like the following just works!
Keep in mind if you use a Javascript framework like Vue or React and you’re wanting to pass your context data into a custom component, then you’ll need to use force_escape. Example:
So why not use Django’s json_script template tag?
The short answer is you can use the json_script template tag, but there are some things to consider.
First, if you look at the actual code you’ll see that the filter wraps the code in a script tag
which I’ve found to not be necessary. Secondly, there isn’t a great way to use a subclassed DjangoJSONEncoder
that we
need if there are custom data types that the default DjangoJSONEncoder
doesn’t handle.
What about security?
You should always be mindful of security and test your application to make sure that it hasn’t created a security
exploit. I also recommend reading Django’s excellent security documentation. Having said that, in our
example filter, to_json()
I’m using mark_safe, which I’m fine with because I’ve read and know the
precautions to take. First, under no circumstances pass user-submitted data to the to_json()
filter unless you know
the data has been sanitized with something like bleach. Secondly, write some tests and test your application
manually for XSS exploits.
I should also mention that in our example data we’re using User.objects.all()
for illustrative purposes only. However,
those with a keen eye might recognize that this would send the hashed password to the template, which I don’t
recommend. Instead, to avoid this I recommend you something like
User.objects.values_list('first_name', 'last_name', 'email')
or something equivalent.
Final Thoughts
Hopefully, this post will end up saving engineers a lot of time and prevent a lot of bugs from being created. I also
hope that maybe Django could either include some of this information in the documentation or add a template filter
like to_json
to Django that could be easily extended with your project’s own default encoder.