Django's New JSONField Is Awesome

Perhaps you have a profiles table with 5-10 really specific columns (e.g., blog_url, pet_name, favorite_framework_1, favorite_framework_2, etc.) with empty values in half the rows? Or, maybe you're a purist like me and you've just left some features out to avoid bloating your tables.

PostgreSQL 9.4 adds the jsonb data type[3, 4]. Now you've got one of the great features of Mongo, et al – the natively supported ability to store and efficiently query data in a valid JSON format – and you can attach it right to a table in an ACID compliant database.

Fast-forward to Django 1.9

Django 1.9 (alpha release is in roughly 1 week) adds support for Postgres's new jsonb data type via JSONField, and that's a bigger deal than you'd guess based on the quick mention it gets in the release notes.

In fact, it's amazing:

Profile.objects.create(
name='Michael Angeletti',
info={
'languages': [
{
'name': 'English',
'level': 'native'
},
{
'name': 'French',
'level': 'un peu'
}
],
'websites': [
'http://orokusaki.posthaven.com',
'http://stackoverflow.com/users/128463/orokusaki'
],
'city': 'Jupiter'
}
)
Before JSONField, the above example would require language and website tables and a Profile.city field to be as robust, and the city field would probably remain empty in most rows anyway. HStoreField was added in Django 1.8, but hstore only supports strings (e.g., a list of websites) and you can't nest values. In the above example, HStoreField covers the Profile.city case, but not the others.

In my personal experience developing an ecommerce shopping cart app in the past, I would have used this JSONField to store payment transaction data (e.g., from Stripe), had it been available, but in the shopping cart I probably wouldn't have stored something like an address in this way, since an address is a predictable format. Other things a hosted ecommerce app could use this for are configuring settings for a merchant (e.g., site title, colors, etc.).

Although Django's new jsonb support handles serialization, deserialization and validation for you, the real magic is in the ORM's query support for this new data type:
>>> Profile.objects.filter(info__city='Jupiter')
[<Profile: Michael Angeletti>]
>>> Profile.objects.filter(info__websites__contains='http://orokusaki.posthaven.com')
[<Profile: Michael Angeletti>]
>>> Profile.objects.filter(info__languages__contains={'name': 'French', 'level': 'un peu'})
[<Profile: Michael Angeletti>]
>>> Profile.objects.filter(info__languages__contains={'name': 'French', 'level': 'expert'})
[]