Eric Qian
Eric Qian

Reputation: 2256

Is there a better way to convert a Model to a well formatted JSON string?

I am new to Python and Django, and I just followed the tutorial on Django Book, and I created three Models according to the tutorial - Publisher, Author, Book. Now I want to get all the books, and encoded them to a JSON string. At first, I just use the method I found on djangoproject.com. Here is the code:

from django.core import serializers

def getAllBooks(request):
    book_list = Book.objects.all()
    return HttpResponse(serializers.serialize("json", book_list), content_type="application/json")

It works fine, but the result is like this:

[
    {
        "pk": 1,
        "model": "books.book",
        "fields": {
            "publisher": 1,
            "title": "Book One",
            "authors" : [3, 4],
            "publication_date": "2013-07-01"
        }
    },
    {
        "pk": 2,
        "model": "books.book",
        "fields": {
            "publisher": 3,
            "title": "Book Two",
            "authors" : [5],
            "publication_date": "2013-07-05"
        }
    }
]

We can see the authors and publisher only show the id. Then I read through that article on djangoproject.com. At the end, it introduces a method called natural_key. By using that method, the authors field will look like this:

 ....
    {
        "pk": 1,
        "model": "books.book",
        "fields": {
            "publisher": 1,
            "title": "Book One",
            "authors" : ["Douglas", "Adams"],
            "publication_date": "2013-07-01"
        }
    },
....

It's better, but still not what I exactly want. What I want is this:

[
    {
    "publisher":{
        "website":"http://www.example.com/",
        "city":"SYD",
        "name":"Publisher",
        "country":"AU",
        "state":"NSW",
        "address":"1 Avenue"
    },
    "authors":[
        {
            "first_name":"Eric",
            "last_name":"Qian",
            "email":"[email protected]"
        },
        {
            "first_name":"Eric2",
            "last_name":"Qian",
            "email":"[email protected]"
        }
    ],
    "publication_date":"01/07/2013",
    "title":"Book One"
    }
]

The authors and publisher fields include all the data. I achieved this by adding a method called JSONEncode to all the models like this:

from django.db import models

# Create your models here.
class Publisher(models.Model):
    name = models.CharField(max_length=30)
    address = models.CharField(max_length=50)
    city = models.CharField(max_length=60)
    state_province = models.CharField(max_length=30)
    country = models.CharField(max_length=50)
    website = models.URLField()

    def __unicode__(self):
        return self.name

    class Meta:
        ordering = ['name']

    def JSONEncode(self):
        #init a dictionary
        JSONData = {};
        #name
        JSONData['name'] = self.name
        #address
        JSONData['address'] = self.address
        #city
        JSONData['city'] = self.city
        #state_province
        JSONData['state'] = self.state_province
        #country
        JSONData['country'] = self.country
        #website
        JSONData['website'] = self.website
        #return the json data
        return JSONData

class Author(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=40)
    email = models.EmailField()

    def __unicode__(self):
        return u'%s %s' % (self.first_name, self.last_name)

    def JSONEncode(self):
        #init a dictionary
        JSONData = {};
        #first_name
        JSONData['first_name'] = self.first_name
        #last_name
        JSONData['last_name'] = self.last_name
        #email
        JSONData['email'] = self.email
        #return the json data
        return JSONData

class Book(models.Model):
    title = models.CharField(max_length=100)
    authors = models.ManyToManyField(Author)
    publisher = models.ForeignKey(Publisher)
    publication_date = models.DateField()

    def __unicode__(self):
        return self.title

    class Meta:
        ordering = ['title']

    def JSONEncode(self):
        #init a dictionary
        JSONData = {};
        #title
        JSONData['title'] = self.title
        #authors
        authors = []
        for author in self.authors.all():
        authors.append(author.JSONEncode())
        JSONData['authors'] = authors
        #publisher
        JSONData['publisher'] = self.publisher.JSONEncode()
        JSONData['publication_date'] = self.publication_date.strftime('%d/%m/%Y')
        #return the json data
        return JSONData

Then I modify the code in books.views:

def getAllBooks(request):
    book_list = Book.objects.all()
    book_list_data = []
    for book in book_list:
        book_list_data.append(book.JSONEncode())
    return HttpResponse(json.dumps(book_list_data), content_type="application/json")

It works quite good, but the draw back is obvious - I have to write a JSONEncode() function to all models. So I am wondering is Django provide a better way to do this? Thanks in advance!

Upvotes: 2

Views: 127

Answers (2)

Anton C
Anton C

Reputation: 86

  • Option 1 is Django-piston. By specifying model fields manually you can get nested listings and foreign key following. Probably the fastest way to get what you want, minimum errors, but not really flexible.

  • Option 2 if to do model serialization in naïve way by your own. For each model define a method that would convert itself to python dictionary, and then use simplejson.dumps in views.py to make it JSON. This approach gives you full control and infinite flexibility deciding about your JSON structure. Later you can replace it with more elegant solution using multiple inheritance to add as_dict and define own JsonResponse class.

Example:

# In models.py, add as_dict() method to all models
# Example for class Book
def as_dict(self):
    d = {
        "id": self.id,
        "publisher": self.publisher.as_dict(), # avoid this
        "title": self.title,        
        "publication_date": str(self.publication_date),  
        "publisher": self.publisher,  
        "authors": [author.as_dict() for author in self.authors.all()] # avoid this
    }

# then in views.py
def getAllBooks(request):
    book_list = [book.as_dict() for book in Book.objects.all().select_related()]
    return HttpResponse(simplejson.dumps(book_list), 
                        content_type="application/json")

HOWEVER

Both approaches are kind of dirty. Your database may and probably will struggle because of this. Django ORM will produce ugly sql. Best is to avoid nested model serialization at all. And if you really need it, dont forget to select_related().

Good luck!

Upvotes: 1

AndrewS
AndrewS

Reputation: 1666

You could try using Tastypie or django-rest-framework. You can customise the JSON emitted. Whilst it adds another layer of complexity it will probably pay off in the long run.

Upvotes: 1

Related Questions