Login

JSONField

Author:
Jasber
Posted:
May 2, 2009
Language:
Python
Version:
1.0
Score:
3 (after 3 ratings)

This is a custom field that lets you easily store JSON data in one of your model fields. This is updated to work with Django 1.1.

Example: (models.py)

from django.db import models
import JSONField

class MyModel(models.Model):
    info = JSONField()

Example: (shell)

>>> obj = MyModel.objects.all()[0]
>>> type(obj.info)
<type 'NoneType'>
>>> obj.info = {"test": [1, 2, 3]}
>>> obj.save()

Code at GitHub

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from django.db import models
from django.core.serializers.json import DjangoJSONEncoder
from django.utils import simplejson as json

class JSONField(models.TextField):
    """JSONField is a generic textfield that neatly serializes/unserializes
    JSON objects seamlessly"""

    # Used so to_python() is called
    __metaclass__ = models.SubfieldBase

    def to_python(self, value):
        """Convert our string value to JSON after we load it from the DB"""

        if value == "":
            return None

        try:
            if isinstance(value, basestring):
                return json.loads(value)
        except ValueError:
            pass

        return value

    def get_db_prep_save(self, value):
        """Convert our JSON object to a string before we save"""

        if value == "":
            return None

        if isinstance(value, dict):
            value = json.dumps(value, cls=DjangoJSONEncoder)

        return super(JSONField, self).get_db_prep_save(value)

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 3 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 7 months ago

Comments

cpesch (on May 6, 2009):

I have problems loading json (with loaddata) that has been dumped (with dumpdata) from a PostgreSQL database. The to_python method is given a value like:

{u'source': u'web', u'location': u'home'}

and this leads to the error message:

ValueError: Expecting property name: line 1 column 1 (char 1)

loads() seems to expect something like

{"source": "web", "location": "home"}

Inserting something like

value = value.replace("u'", "'").replace("'",'"')

before line 19 cures the problem, but that doesn't look like an elegant solution, isn't it?

#

jb0t (on May 13, 2009):

This worked a little better for me. Though I am still not all that happy with it.

# # # # # # # # # # # # # # # # #


from django.db import models
from django.core.serializers.json import DjangoJSONEncoder
from django.utils import simplejson as json

class JSONField(models.TextField):
    """JSONField is a generic textfield that neatly serializes/unserializes
    JSON objects seamlessly

    The json spec claims you must use a collection type at the top level of
    the data structure.  However the simplesjon decoder and Firefox both encode
    and decode non collection types that do not exist inside a collection.
    The to_python method relies on the value being an instance of basestring
    to ensure that it is encoded.  If a string is the sole value at the
    point the field is instanced, to_python attempts to decode the sting because
    it is derived from basestring but cannot be encodeded and throws the
    exception ValueError: No JSON object could be decoded.
    """

    # Used so to_python() is called
    __metaclass__ = models.SubfieldBase

    def to_python(self, value):
        """Convert our string value to JSON after we load it from the DB"""
        if value == "":
            return None

        try:
            if isinstance(value, basestring):
                return json.loads(value)
        except ValueError:
            return value

        return value

    def get_db_prep_save(self, value):
        """Convert our JSON object to a string before we save"""

        if value == "":
            return None

        return super(JSONField, self).get_db_prep_save(json.dumps(value, cls=DjangoJSONEncoder))

#

jb0t (on May 13, 2009):

Last sentence should actually read

If a string is the sole value at the point the field is instanced, to_python attempts to decode the sting because it is derived from basestring but cannot be decoded

#

Jasber (on May 20, 2009):

jb0t: I updated what you added, thanks.

cpesch: I'm really not sure, I don't have PostgreSQL installed on my machine. Also no, that doesn't look like a very elegant solution. If you can come up with something let me know and I'll add it.

#

fmardini (on June 18, 2009):

a hopefully elegant solution :)

value = dict(map(lambda n: map(str, n), value.items()))

#

Jasber (on June 24, 2009):

fmardini: I think that may cover most use cases, but simply mapping complex objects to str isn't going to work for everyone.

#

chowdere (on August 11, 2009):

I was being thrown for a loop with e.g. "Error binding parameter" (using sqlite) when trying to save a model object that had a JSONField. Turns out I was passing in a list, not a dict (valid JSON), and this JSONField doesn't handle lists. The quick fix has line 32 become:

        if isinstance(value, dict) or isinstance(value, list):

#

andrewbadr (on November 9, 2009):

I'm only using JSONField for holding dictionaries, which I suspect is pretty common. So, I took out the other options. Use like "properties = DictField(default={})".

class DictField(models.TextField):
"""DictField is a textfield that contains JSON-serialized dictionaries."""

# Used so to_python() is called
__metaclass__ = models.SubfieldBase

def to_python(self, value):
    """Convert our string value to JSON after we load it from the DB"""
    value = json.loads(value)
    assert isinstance(value, dict)
    return value

def get_db_prep_save(self, value):
    """Convert our JSON object to a string before we save"""
    assert isinstance(value, dict)
    value = json.dumps(value, cls=DjangoJSONEncoder)
    return super(DictField, self).get_db_prep_save(value)

#

g4b (on July 7, 2010):

modified it, so it can hold lists and does serialize (at least i tried json serialization) also modified after the docs to use get_prep_value, since they write its the function to use as antidote to to_python.

class JSONField(models.TextField): """JSONField is a generic textfield that neatly serializes/unserializes JSON objects seamlessly"""

# Used so to_python() is called
__metaclass__ = models.SubfieldBase

def to_python(self, value):
    """Convert our string value to JSON after we load it from the DB"""

    if value == "":
        return None

    try:
        if isinstance(value, basestring):
            return json.loads(value)
    except ValueError:
        pass

    return value

def get_prep_value(self, value):
    """Convert our JSON object to a string before we save"""
    if value == "":
        return None
    if isinstance(value, dict) or isinstance(value, dict):
        value = json.dumps(value, cls=DjangoJSONEncoder)
    return super(JSONField, self).get_prep_value(value)

def value_to_string(self, obj):
    """ called by the serializer.
    """
    value = self._get_val_from_obj(obj)
    return self.get_db_prep_value(value)

#

hijinks (on May 11, 2012):

Fix for 1.4. Need to add connection into the get_db_prep_save()

def get_db_prep_save(self, value, connection):
    """Convert our JSON object to a string before we save"""

    if value == "":
        return None

    if isinstance(value, dict):
        value = json.dumps(value, cls=DjangoJSONEncoder)

    return super(JSONField, self).get_db_prep_save(value, connection)

#

Please login first before commenting.