Skip to content

Commit b3e0259

Browse files
authored
Add support for Django 3.1 JSONField (#7467)
Django 3.1 adds a new generic JSONField to replace the PostgreSQL-specific one. This adds support for the new field type, which should behave the same as the existing PostgreSQL field. Django's new JSONField also includes support for a custom "decoder", so add support for that in the serializer field.
1 parent 7f3a355 commit b3e0259

File tree

4 files changed

+37
-4
lines changed

4 files changed

+37
-4
lines changed

rest_framework/fields.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1758,6 +1758,7 @@ class JSONField(Field):
17581758
def __init__(self, *args, **kwargs):
17591759
self.binary = kwargs.pop('binary', False)
17601760
self.encoder = kwargs.pop('encoder', None)
1761+
self.decoder = kwargs.pop('decoder', None)
17611762
super().__init__(*args, **kwargs)
17621763

17631764
def get_value(self, dictionary):
@@ -1777,7 +1778,7 @@ def to_internal_value(self, data):
17771778
if self.binary or getattr(data, 'is_json_string', False):
17781779
if isinstance(data, bytes):
17791780
data = data.decode()
1780-
return json.loads(data)
1781+
return json.loads(data, cls=self.decoder)
17811782
else:
17821783
json.dumps(data, cls=self.encoder)
17831784
except (TypeError, ValueError):

rest_framework/serializers.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -884,6 +884,8 @@ class ModelSerializer(Serializer):
884884
models.GenericIPAddressField: IPAddressField,
885885
models.FilePathField: FilePathField,
886886
}
887+
if hasattr(models, 'JSONField'):
888+
serializer_field_mapping[models.JSONField] = JSONField
887889
if postgres_fields:
888890
serializer_field_mapping[postgres_fields.HStoreField] = HStoreField
889891
serializer_field_mapping[postgres_fields.ArrayField] = ListField
@@ -1242,10 +1244,13 @@ def build_standard_field(self, field_name, model_field):
12421244
# `allow_blank` is only valid for textual fields.
12431245
field_kwargs.pop('allow_blank', None)
12441246

1245-
if postgres_fields and isinstance(model_field, postgres_fields.JSONField):
1247+
is_django_jsonfield = hasattr(models, 'JSONField') and isinstance(model_field, models.JSONField)
1248+
if (postgres_fields and isinstance(model_field, postgres_fields.JSONField)) or is_django_jsonfield:
12461249
# Populate the `encoder` argument of `JSONField` instances generated
1247-
# for the PostgreSQL specific `JSONField`.
1250+
# for the model `JSONField`.
12481251
field_kwargs['encoder'] = getattr(model_field, 'encoder', None)
1252+
if is_django_jsonfield:
1253+
field_kwargs['decoder'] = getattr(model_field, 'decoder', None)
12491254

12501255
if postgres_fields and isinstance(model_field, postgres_fields.ArrayField):
12511256
# Populate the `child` argument on `ListField` instances generated

rest_framework/utils/field_mapping.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ def get_field_kwargs(field_name, model_field):
9292
kwargs['allow_unicode'] = model_field.allow_unicode
9393

9494
if isinstance(model_field, models.TextField) and not model_field.choices or \
95-
(postgres_fields and isinstance(model_field, postgres_fields.JSONField)):
95+
(postgres_fields and isinstance(model_field, postgres_fields.JSONField)) or \
96+
(hasattr(models, 'JSONField') and isinstance(model_field, models.JSONField)):
9697
kwargs['style'] = {'base_template': 'textarea.html'}
9798

9899
if isinstance(model_field, models.AutoField) or not model_field.editable:

tests/test_model_serializer.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"""
88
import datetime
99
import decimal
10+
import json # noqa
1011
import sys
1112
import tempfile
1213
from collections import OrderedDict
@@ -478,6 +479,7 @@ class Meta:
478479
""")
479480
self.assertEqual(repr(TestSerializer()), expected)
480481

482+
@pytest.mark.skipif(hasattr(models, 'JSONField'), reason='has models.JSONField')
481483
def test_json_field(self):
482484
class JSONFieldModel(models.Model):
483485
json_field = postgres_fields.JSONField()
@@ -496,6 +498,30 @@ class Meta:
496498
self.assertEqual(repr(TestSerializer()), expected)
497499

498500

501+
class CustomJSONDecoder(json.JSONDecoder):
502+
pass
503+
504+
505+
@pytest.mark.skipif(not hasattr(models, 'JSONField'), reason='no models.JSONField')
506+
class TestDjangoJSONFieldMapping(TestCase):
507+
def test_json_field(self):
508+
class JSONFieldModel(models.Model):
509+
json_field = models.JSONField()
510+
json_field_with_encoder = models.JSONField(encoder=DjangoJSONEncoder, decoder=CustomJSONDecoder)
511+
512+
class TestSerializer(serializers.ModelSerializer):
513+
class Meta:
514+
model = JSONFieldModel
515+
fields = ['json_field', 'json_field_with_encoder']
516+
517+
expected = dedent("""
518+
TestSerializer():
519+
json_field = JSONField(decoder=None, encoder=None, style={'base_template': 'textarea.html'})
520+
json_field_with_encoder = JSONField(decoder=<class 'tests.test_model_serializer.CustomJSONDecoder'>, encoder=<class 'django.core.serializers.json.DjangoJSONEncoder'>, style={'base_template': 'textarea.html'})
521+
""")
522+
self.assertEqual(repr(TestSerializer()), expected)
523+
524+
499525
# Tests for relational field mappings.
500526
# ------------------------------------
501527

0 commit comments

Comments
 (0)