JSON serialization of Google App Engine models

For some AJAX requests you’ll need an rpc urls returning a serialized objects. In principle its simple just use JSON serialization build in in Django or simplejson module itself. Here are sample view methods:

def entity_list(request, entity_key=None):
    user = users.get_current_user().email().lower();
    col = models.Entity.gql('WHERE user=:1',user).fetch(300, 0)

    json = serializers.serialize("json", col)
    return HttpResponse(json, content_type='application/json; charset=%s' % settings.DEFAULT_CHARSET)

def entity_autocomplete(request):
    user = users.get_current_user().email().lower();
    col = models.Entity.gql('WHERE user=:1',user).fetch(300, 0)

    json = simplejson.dumps(col)
    return HttpResponse(json, content_type='application/json; charset=%s' % settings.DEFAULT_CHARSET)

But there’s a catch. Django will only serialize properties no other fields (eg. computed fields or properties) will be serialized, and simplejson will complain that your Entity is not serializable.

In the first case if you don’t need anything else than data store properties, you’re ok. I wanted the get_absolute_url() to be serialized, it comes handy on client side and it’s very DRY if do not compute urls on client side. To do that I have overridden a default JSON serialization module, creating a json_serializer.py:

import datetime
import logging
from StringIO import StringIO

from django.core.serializers.python import Serializer as PythonSerializer
from django.core.serializers.python import Deserializer as PythonDeserializer
from django.utils.encoding import smart_str, smart_unicode
from django.utils import datetime_safe
from django.utils import simplejson

from django.core.serializers.json import Serializer as JsonSerializer

class Serializer(JsonSerializer):
    """
    A JSON serializer suporting get_absolute_url serialization.
    """  
    extra_methods = {"get_absolute_url":"absolute_url"}

    def serialize(self, queryset, **options):
        """
        Serialize a queryset.
        """
        logging.debug(queryset)
        self.options = options

        self.stream = options.get("stream", StringIO())
        self.selected_fields = options.get("fields")

        self.start_serialization()
        for obj in queryset:
            self.start_object(obj)
            for field in obj._meta.local_fields:
                if field.serialize:
                    if field.rel is None:
                        if self.selected_fields is None or field.attname in self.selected_fields:
                            self.handle_field(obj, field)
                    else:
                        if self.selected_fields is None or field.attname[:-3] in self.selected_fields:
                            self.handle_fk_field(obj, field)
            for field in obj._meta.many_to_many:
                if field.serialize:
                    if self.selected_fields is None or field.attname in self.selected_fields:
                        self.handle_m2m_field(obj, field)
            for method_name in self.extra_methods:
                  if self.selected_fields is None or method_name in self.selected_fields:
                      self.handle_method(obj, method_name, self.extra_methods[method_name])
            self.end_object(obj)
        self.end_serialization()
        return self.getvalue()

    def handle_method(self, obj, method_name, data_name):
        logging.debug(method_name)
        data = getattr(obj, method_name)()
        logging.debug(data)
        if isinstance(data, (list, tuple)):
            serialized = [smart_unicode(item, strings_only=True) for item in data]
        else:
            serialized = smart_unicode(data, strings_only=True)
        self._current[data_name] = serialized

Put it in the settings:

SERIALIZATION_MODULES = { 'json' : 'json_serializer' }

In the second case, all I wanted was name:key pairs to be serialized, this was simpler. I have provided a default argument with callable doing the serialization for my type, simple lambda expression did the trick:

def entity_autocomplete(request):
  user = users.get_current_user().email().lower();
  col = models.Entity.gql('WHERE user=:1',user).fetch(300, 0)

  json = simplejson.dumps(col, default=lambda o: {o.name :str(o.key())})
  return HttpResponse(json, content_type='application/json; charset=%s' % settings.DEFAULT_CHARSET)

Probably this won’t help you right away, but I hope this will get you started :)

Dodaj komentarz