Skip to content

Commit 73808f6

Browse files
Added laziness option
1 parent 7ca9df4 commit 73808f6

File tree

4 files changed

+40
-15
lines changed

4 files changed

+40
-15
lines changed

README.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ Add a ``JSONField`` to your model like any other field.
6565

6666
- ``default``: Falls back on ``"null"`` if not provided and ``null=False``, otherwise ``None``
6767
- ``db_type``: Allows you to specify the column type (default: ``text``)
68+
- ``lazy``: Defer deserialization until the field is directly accessed (default: ``True``)
6869
- ``encoder``: Custom JSON encoder (default: ``DjangoJSONEncoder``)
6970
- ``decoder``: Custom JSON decoder (default: ``json_field.fields.JSONDecoder``)
7071
- ``encoder_kwargs``: Specify all arguments to the encoder (overrides ``encoder``)

json_field/fields.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -67,33 +67,35 @@ class Creator(object):
6767
Taken from django.db.models.fields.subclassing.
6868
"""
6969

70-
_parent_key = '_json_field_cache'
70+
_state_key = '_json_field_state'
7171

72-
def __init__(self, field):
72+
def __init__(self, field, lazy):
7373
self.field = field
74+
self.lazy = lazy
7475

7576
def __get__(self, obj, type=None):
7677
if obj is None:
7778
raise AttributeError('Can only be accessed via an instance.')
7879

79-
cache = getattr(obj, self._parent_key, None)
80-
if cache is None:
81-
cache = {}
82-
setattr(obj, self._parent_key, cache)
80+
if self.lazy:
81+
state = getattr(obj, self._state_key, None)
82+
if state is None:
83+
state = {}
84+
setattr(obj, self._state_key, state)
8385

84-
key = '%s_deserialized' % self.field.name
86+
if state.get(self.field.name, False):
87+
return obj.__dict__[self.field.name]
8588

86-
if cache.get(key, False):
87-
return obj.__dict__[self.field.name]
88-
89-
value = self.field.to_python(obj.__dict__[self.field.name])
90-
obj.__dict__[self.field.name] = value
91-
cache[key] = True
89+
value = self.field.to_python(obj.__dict__[self.field.name])
90+
obj.__dict__[self.field.name] = value
91+
state[self.field.name] = True
92+
else:
93+
value = obj.__dict__[self.field.name]
9294

9395
return value
9496

9597
def __set__(self, obj, value):
96-
obj.__dict__[self.field.name] = value # deserialized when accessed
98+
obj.__dict__[self.field.name] = value if self.lazy else self.field.to_python(value)
9799

98100
class JSONField(models.TextField):
99101
""" Stores and loads valid JSON objects. """
@@ -107,6 +109,7 @@ def __init__(self, *args, **kwargs):
107109
self._db_type = kwargs.pop('db_type', None)
108110
self.evaluate_formfield = kwargs.pop('evaluate_formfield', False)
109111

112+
self.lazy = kwargs.pop('lazy', True)
110113
encoder = kwargs.pop('encoder', DjangoJSONEncoder)
111114
decoder = kwargs.pop('decoder', JSONDecoder)
112115
encoder_kwargs = kwargs.pop('encoder_kwargs', {})
@@ -170,7 +173,7 @@ def set_json(model_instance, value):
170173
return setattr(model_instance, self.attname, self.to_python(value))
171174
setattr(cls, 'set_%s_json' % self.name, set_json)
172175

173-
setattr(cls, name, Creator(self)) # deferred deserialization
176+
setattr(cls, name, Creator(self, lazy=self.lazy)) # deferred deserialization
174177

175178
try:
176179
# add support for South migrations

test_project/app/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@
55
class Test(models.Model):
66

77
json = JSONField()
8+
json_eager = JSONField(lazy=False)
89
json_null = JSONField(blank=True, null=True, evaluate_formfield=True)

test_project/app/tests.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,26 @@ def test_simple(self):
3333
t8.json = {'asdf':123}
3434
self.assertEqual({'asdf':123}, t8.json)
3535

36+
def test_eager(self):
37+
t1 = Test.objects.create(json_eager=123)
38+
self.assertEqual(123, Test.objects.get(pk=t1.pk).json_eager)
39+
t2 = Test.objects.create(json_eager='123')
40+
self.assertEqual(123, Test.objects.get(pk=t2.pk).json_eager)
41+
t3 = Test.objects.create(json_eager=[123])
42+
self.assertEqual([123], Test.objects.get(pk=t3.pk).json_eager)
43+
t4 = Test.objects.create(json_eager='[123]')
44+
self.assertEqual([123], Test.objects.get(pk=t4.pk).json_eager)
45+
t5 = Test.objects.create(json_eager={'test':[1,2,3]})
46+
self.assertEqual({'test':[1,2,3]}, Test.objects.get(pk=t5.pk).json_eager)
47+
t6 = Test.objects.create(json_eager='{"test":[1,2,3]}')
48+
self.assertEqual({'test':[1,2,3]}, Test.objects.get(pk=t6.pk).json_eager)
49+
t7 = Test.objects.create(json_eager=[1,2,3])
50+
t7.json_eager = {'asdf':123}
51+
self.assertEqual({'asdf':123}, t7.json_eager)
52+
t8 = Test.objects.get(pk=t7.pk)
53+
t8.json_eager = {'asdf':123}
54+
self.assertEqual({'asdf':123}, t8.json_eager)
55+
3656
def test_null(self):
3757
t1 = Test.objects.create(json=None)
3858
self.assertEqual(None, t1.json)

0 commit comments

Comments
 (0)