Passing a JSON object from Flask to JavaScript

36,905

Solution 1

It seems that the currently accepted answer (by @BrettJ) has a possible security flaw in it: if the object we pass to javascript has some string with a single quote inside, this single quote will not be escaped by json.dumps, thus allowing to inject arbitrary code into javascript. It is better to use Flask's tojson() template filter, see the docs, as it makes proper escaping of all such characters (replace them with unicode codes).

Here is my solution:

view.py

from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def hello_world():
    user = {'firstname': "Mr.", 'lastname': "My Father's Son"}
    return render_template("index.html", user=user)

if __name__ == '__main__':
    app.run()

index.html

<p>Hello, <span id="username"></span></p>
<script>
    var user = JSON.parse('{{ user | tojson | safe}}');
    document.getElementById('username').innerHTML = user.firstname + " " +
            user.lastname;
</script>

Generated JS looks like:

var user = JSON.parse('{"firstname": "Mr.", "lastname": "My Father\u0027s Son"}');

which is perfectly safe. For example, if we'd use json.dumps-powered solution, we'd get

var user = JSON.parse('{"firstname": "Mr.", "lastname": "My Father's Son"}');

which is syntactically incorrect (to say the least).

Solution 2

The below simple example should show how to get a Javascript object from your dict:

views.py

@app.route('/', methods=['GET','POST'])                                         
def index():                                                                    

    points = [{"lat": 43.8934276, "lng": -103.3690243},                         
              {"lat": 47.052060, "lng": -91.639868},                            
              {"lat": 45.1118, "lng": -95.0396}]                                

    return render_template("index.html", points=json.dumps(points)) 

index.html (some code removed for brevity)

  function initMap() {                                                      

    var map = new google.maps.Map(document.getElementById('map'), {         
      center: new google.maps.LatLng(43.8934276, -103.3690243),             
      zoom: 4                                                               
    });                                                                     

    var points = JSON.parse('{{ points|safe }}');                           
    var marker;                                                             

    for (var i = 0; i < points.length; i++) {                               

        marker = new google.maps.Marker({                                   
          position: new google.maps.LatLng(points[i].lat, points[i].lng),   
          map: map                                                          
        });                                                                 

    }                                                                       
  }   
Share:
36,905

Related videos on Youtube

BrettJ
Author by

BrettJ

I have a pragmatic relationship with code, meaning I don't pay the bills writing it but I spend less time doing so because of it.

Updated on July 29, 2022

Comments

  • BrettJ
    BrettJ almost 2 years

    I'm having troubles getting a Flask/Python variable passed to Javascript.

    Basically, I'm importing from MySQL and have tried rendering the return in three different ways.

    1. (43.8934276, -103.3690243), (47.052060, -91.639868), (45.1118, -95.0396) that is the output when my dict item has the following ran on it.

    new_list = [tuple(d.values()) for d in MySQL_Dict] output = ', '.join('(' + ', '.join(i) + ')' for i in new_list)

    This method is no good, but I added it for detail, it's not in the right format at all.

    1. I pass the python dict directly to the template which looks like this

    ({'lat': '43.8934276', 'lng': '-103.3690243'}, {'lat': '47.052060', 'lng': '-91.639868'}, {'lat': '45.1118', 'lng': '-95.0396'})

    Then on the the template side I've tried the following JavaScript lines

     var other_coords = {{ MySQL_Dict|tojson }}; 
     var other_coords = {{ MySQL_Dict|tojson|safe }};
     var still_more = JSON.parse(other_coords);
    

    None of which work, together or separately.

    1. I've also tried sending the dictionary from the view using json_out = json.dumps(My_Dict) which does not work either.

    This is all with the goal of getting the lat, lng coords from the MySQL DB to the Google Maps API script. The thing that is so confusing to me is that if I just paste the json.dump results from the view into the Google Maps script it works perfectly (after the quotes are removed) but if I use a variable it will not work for me. Does anyone have suggestions?

    • Matt Healy
      Matt Healy about 7 years
      What is the desired output in the rendered HTML?
    • BrettJ
      BrettJ about 7 years
      Markers on a Google Map.
    • Matt Healy
      Matt Healy about 7 years
      Sorry I mean what output format (coordinates) are you looking for?
    • BrettJ
      BrettJ about 7 years
      Oh sorry, it needs to look like this, just a JavaScript object, correct? Which I thought you could get from JSON by using the JSON.parse(json_object)? [{lat: 43.8934276, lng: -103.3690243}, {lat: 47.052060, lng: -91.639868}, {lat: 45.1118, lng: -95.0396}]
  • BrettJ
    BrettJ about 7 years
    Thank you for the answer! Ok, so why is this happening? If I do everything as you say the markers do not appear on the map. In Google Maps I'm just setting var locations = points; However, if I copy and paste the output of the points var defined by you, and do var locations = [{lat: 43.8934276,lng: -103.3690243 },{lat: 47.05206,lng: -91.639868}, {lat:45.1118,lng: -95.0396 }] directly into the maps script it works perfect!... I'm so confused.
  • Matt Healy
    Matt Healy about 7 years
    I have added some sample Javascript for rendering the markers on the map. Tested locally and it renders the markers for me. Hope this helps!
  • BrettJ
    BrettJ about 7 years
    You sir are a god! Thank you so much. My biggest issue all this time was actual map script itself... how disappointing. Thanks a ton!!
  • BrettJ
    BrettJ about 7 years
    Also, note to self and/or future people with this issue. I had an underscore in my variable which JS didn't appreciate. Once I changed that, things worked much better. I never write JS so I'm not sure if this is a rule or syntax issue or what. Anyway, thanks again Matt.
  • Ilya V. Schurov
    Ilya V. Schurov almost 7 years
    There is a security issue in this solution (possible arbitrary javascript code injection) due to the lack of escaping of single quote character. It is better to use tojson Flask template filter. See my answer for details.
  • BrettJ
    BrettJ almost 7 years
    Your answer works for me. I changed my accepted answer, better security always equals better code.
  • Teemo
    Teemo over 6 years
    Thank you very much! You saved me a lot of time! :)
  • Elliptica
    Elliptica about 4 years
    This line JSON.parse('{{ user | tojson | safe}}') was exactly what I needed. The single-quotes helped too!
  • hbrannan
    hbrannan almost 4 years
    I'm curious to get clarity about the security vulnerability point raised by @Ilya V. Schurov ("this single quote will not be escaped by json.dumps, thus allowing to inject arbitrary code into javascript"). You are pointing out that the vulnerability is that the unescaped code passed by json dumps itself can itself be code injectable into javascript. Correct?
  • Ilya V. Schurov
    Ilya V. Schurov almost 4 years
    @hbrannan yes, the intruder can put malicious javascript code into fields that they set (i.e. user name during registration, or message text, or something else), and this code can steal cookies/sessions, etc.