22

We have an ndb model that we would like to make json serializable. The models are fairly simple along the lines of:

class Pasta(ndb.Model):
   name = ndb.StringProperty()
   type = ndb.StringProperty()
   comments = ndb.JsonProperty()

Then on the handler side we would like to do something along the lines of:

json.dumps(Pasta.query(Pasta.name=="Ravioli").fetch()) and return it to the client, but it keeps throwing json parse errors since the class Pasta is not json serializable. So, the question is, do we have to implement __str__ or __repr__ or is there a niftier way to do it?

Chris Martin
  • 30,334
  • 10
  • 78
  • 137
rdodev
  • 3,164
  • 3
  • 26
  • 34

5 Answers5

59

ndb.Model instances have a to_dict() function: https://developers.google.com/appengine/docs/python/ndb/modelclass#Model_to_dict

the simplest way is:

json.dumps([p.to_dict() for p in Pasta.query(Pasta.name == "Ravioli").fetch()])
aschmid00
  • 7,038
  • 2
  • 47
  • 66
  • 11
    It's probably worth noting that the `to_dict` does not include the `key` in the model. So you may wish to do something like `json.dumps([dict(p.to_dict(), **dict(id=p.key.id())) for p in ...` – Brian M. Hunt May 22 '14 at 13:38
  • 2
    To avoid errors with datetime, use `json.dumps(..., default=str)` – rein Mar 16 '18 at 00:22
10

I don't believe it's documented, but for existing ext.db Models, you can use db.to_dict() (see here).

Be careful though with db.ReferenceProperty's and db.DateTimeProperty's as they will throw errors when you call json.dumps(). The quick solution is a custom JSONEncoder:

from datetime import datetime, date, time
from google.appengine.ext import db

import json

class JSONEncoder(json.JSONEncoder):

    def default(self, o):
        # If this is a key, you might want to grab the actual model.
        if isinstance(o, db.Key):
            o = db.get(o)

        if isinstance(o, db.Model):
            return db.to_dict(o)
        elif isinstance(o, (datetime, date, time)):
            return str(o)  # Or whatever other date format you're OK with...

Then to encode with this:

JSONEncoder().encode(YourModel.all().fetch())
JJ Geewax
  • 10,342
  • 1
  • 37
  • 49
1

Or you can just add a computed property to the model like below:

class Taxes(ndb.Model):
    name = ndb.StringProperty()
    id = ndb.ComputedProperty(lambda self: self.key.id())

then in your get method just call to_dict and you will get the id

rows = [row.to_dict() for row in Taxes.query()]
self.response.write(json.dumps(rows))
0

If there is a ndb.KeyProperty referring a different model, you could very well override dictionary method as shown below

def to_dict(self):
        result = super(Point, self).to_dict()
        result['x']=self.x.get().to_dict()
        result['y']=self.y.get().to_dict()
        return result

And if the ndb.KeyProperty is a list (with repeated set to True), use below method to fetch the referred model using get_multi

def to_dict(self):
       result = super(Figure, self).to_dict()
       result['points']=[p.to_dict() for p in ndb.get_multi(self.points)]
       return result 
Anoop Isaac
  • 932
  • 12
  • 15
0

If there is a ndb.KeyProperty referring a different model, you could very well override dictionary method as shown below

def to_dict(self):
        result = super(Point, self).to_dict()
        result['x']=self.x.get().to_dict()
        result['y']=self.y.get().to_dict()
        return result

And if the ndb.KeyProperty is a list (with repeated set to True), and if you intend to serialize the referred model as well, use below method to fetch the referred model using get_multi

def to_dict(self):
       result = super(Figure, self).to_dict()
       result['points']=[p.to_dict() for p in ndb.get_multi(self.points)]
       return result 
Anoop Isaac
  • 932
  • 12
  • 15