29

I have this in my view:

string_location = myaddress2
    geodata = []
    for place, (lat, lng) in g.geocode(string_location,exactly_one=False):
        geodata.append((place, (lat, lng)))

    geodata_results = len(geodata)

    data = {"geodata": geodata, "geodata_results":geodata_results }
    return render_to_response("business/business_view.html",
                              data, context_instance=RequestContext(request))

How would I "handle" / convert geodata into JSON and pass it to my template so that I can "loop" through it like an array?

Am I right to think that I can do it this way? If not, then please suggest on a better solution.

Thanks!

UPDATE

var geodata = "[["M. L. Quezon Street<br/>Mandaue City, Philippines", [10.351381999999999, 123.923535]], ["Talamban<br/>Cebu City, Philippines", [10.353527, 123.91352500000001]]]"; 

I think the JSON is not escaped? How do I escape special characters inside the json string? I keep getting a newline error.

For PHP, I would json_encode() to fix this. Like in this post: Pass a PHP string to a JavaScript variable (and escape newlines) BUT how do I do that in Python/Django?

Community
  • 1
  • 1
wenbert
  • 5,263
  • 8
  • 48
  • 77

5 Answers5

70

You could use the built-in json module:

>>> import json
>>> geodata = [ ( "Here", (1003,3004) ), ("There", (1.2,1.3)) ]
>>> json.dumps(geodata)
'[["Here", [1003, 3004]], ["There", [1.2, 1.3]]]'

You can then simply embed the resulting string inside a javascript script:

<script type='text/javascript'>
var geodata = {{ geodata|safe }};
</script>
adamk
  • 45,184
  • 7
  • 50
  • 57
  • I get an error after this line: var geodata = "[["M. L. Quezon Street<br/>Mandaue City, Philippines", [10.351381999999999, 123.923535]], ["Talamban<br/>Cebu City, Philippines", [10.353527, 123.91352500000001]]]"; I think the JSON is not escaped? How do I escape special characters inside the json string? – wenbert Jul 27 '10 at 16:24
  • Could it be that you added to your template `var geodata = "{{ geodata }}";`? It should be without the quotes. – adamk Jul 27 '10 at 17:51
  • Nope. I have tried that without the quotes. I get a "Uncaught SyntaxError: Unexpected token &" in Google and this error: "var geodata = [["M. L...;, [10.353527, 123.91352500000001]]];\n" in Firefox – wenbert Jul 27 '10 at 23:26
  • 8
    you must have autoescaping on... try to put in your template `{{ geodata|safe }}` (also fixed in the answer) – adamk Jul 28 '10 at 06:42
  • Why should it be without the quotes? Strings evaluated in Django templates don't have quotes. – Stavros Korokithakis Jun 26 '12 at 13:12
  • You should really worry about XSS exploits when using `|safe`. – Hugo Mota Nov 10 '14 at 14:10
  • 2
    I believe this code has a potential XSS issue. If the JSON contains "", the browser interprets it as the end of – Hiroshi Ichikawa Jul 11 '16 at 04:18
28

Okay, I solved my problem and would like to answer my own question. I figured it would be better for the other users here.

First, get the file here: http://www.JSON.org/json_parse.js

var geodata = json_parse("{{geodata|escapejs}}");

I just used escapejs: http://docs.djangoproject.com/en/dev/ref/templates/builtins/#escapejs

EDIT: Thanks to Ignacio Vazquez-Abrams. It was him that helped me in #python Freenode. Should have credited him when I made this post. I didn't know he was in Stackoverflow.

wenbert
  • 5,263
  • 8
  • 48
  • 77
  • how is this different from using `var geodata = eval("{{geodata|escapejs}}");` ? – simon Mar 30 '11 at 07:41
  • 2
    How is this different from `var geodata = {{ geodata|escapejs }}` (not in a string) ? – Izkata Feb 19 '13 at 17:21
  • 1
    The link to json_parse.js now 404s, but the same approach works with `JSON.parse();`, documented here: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse – bfox May 20 '15 at 23:04
12

If you don't care about old browsers such as IE7, you can simply write:

var geodata = JSON.parse("{{geodata|escapejs}}");

without any extra libraries. See http://caniuse.com/#feat=json for browser versions which support JSON.parse().

I believe the top-voted answer by @adamk has a potential XSS issue. If the JSON contains "</script>", the browser interprets it as the end of <script> tag. So it would be better to use @wenbert 's code or mine.

I tried to comment on the answer directly, but I don't have enough reputation to do that :)

Hiroshi Ichikawa
  • 616
  • 9
  • 10
2

I've found that I often want both the object version (for template code) and the JSON version (for JavaScript code), and find it a bit annoying to pass both separately to the template when one should do fine.

If you do want to take the template tag approach and don't want all the bells and whistles of django argonauts then you can use this template tag which has always done the trick for me. It may not be 100% safe against untrusted data but that's never been an issue for my use cases.

"""
Usage:

{% import json_tags %}

var = myJsObject = {{ template_var|to_json }};

Features:

- Built in support for dates, datetimes, lazy translations.
- Safe escaping of script tags.
- Support for including QueryDict objects.
- Support for custom serialization methods on objects via defining a `to_json()` method.
"""

import datetime
import json
from decimal import Decimal
from django import template
from django.http import QueryDict
from django.utils.encoding import force_str
from django.utils.functional import Promise
from django.utils.safestring import mark_safe

register = template.Library()

ISO_DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%fZ'


def json_handler(obj):
    if callable(getattr(obj, 'to_json', None)):
        return obj.to_json()
    elif isinstance(obj, datetime.datetime):
        return obj.strftime(ISO_DATETIME_FORMAT)
    elif isinstance(obj, datetime.date):
        return obj.isoformat()
    elif isinstance(obj, datetime.time):
        return obj.strftime('%H:%M:%S')
    elif isinstance(obj, Decimal):
        return float(obj)  # warning, potential loss of precision
    elif isinstance(obj, Promise):
        return force_str(obj)  # to support ugettext_lazy
    else:
        return json.JSONEncoder().default(obj)


@register.filter
def to_json(obj):
    def escape_script_tags(unsafe_str):
        # seriously: http://stackoverflow.com/a/1068548/8207
        return unsafe_str.replace('</script>', '<" + "/script>')

    # json.dumps does not properly convert QueryDict array parameter to json
    if isinstance(obj, QueryDict):
        obj = dict(obj)
    return mark_safe(escape_script_tags(json.dumps(obj, default=json_handler)))
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Cory
  • 22,772
  • 19
  • 94
  • 91
1

There is a long standing ticket in django about template filter which would output json in templates. The main problem is that it hard to come up with a solution that can be used in different places of html without introducing XSS. For now the following methods can be used.

Store json in html element data attribute:

<div data-geodata="{{json_dump_of_geodata}}"></div>
<script>
  var geodata = JSON.parse(
      document.querySelectorAll('[data-geodata]')[0].getAttribute('data-geodata')
  );
</script>

Or using https://github.com/fusionbox/django-argonauts

<script>
    var geodata = {{geodata|json}};
</script>

Do not use safe filter until you 100% sure that the json doesn't contain any data from untrusted sources.

UPDATE: In Django 2.1 json_script tag was added as official way to pass json from template context to javascript.