From 9d4d42b6ebb3f9a110d5f329c23aa2a77a501596 Mon Sep 17 00:00:00 2001 From: Raffaele Salmaso Date: Thu, 23 May 2019 14:27:55 +0200 Subject: [PATCH] Allow JSONField encoder customization. --- docs/api-guide/fields.md | 3 ++- rest_framework/fields.py | 5 +++-- rest_framework/serializers.py | 5 +++++ tests/test_model_serializer.py | 7 +++++-- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index b2830d0c9..64515f804 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -501,9 +501,10 @@ Note that the child field **must** be an instance of `CharField`, as the hstore A field class that validates that the incoming data structure consists of valid JSON primitives. In its alternate binary mode, it will represent and validate JSON-encoded binary strings. -**Signature**: `JSONField(binary)` +**Signature**: `JSONField(binary, encoder)` - `binary` - If set to `True` then the field will output and validate a JSON encoded string, rather than a primitive data structure. Defaults to `False`. +- `encoder` - Use this JSON encoder to serialize input object. Defaults to `None`. --- diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 179dd25c8..358aad5ab 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1746,6 +1746,7 @@ class JSONField(Field): def __init__(self, *args, **kwargs): self.binary = kwargs.pop('binary', False) + self.encoder = kwargs.pop('encoder', None) super().__init__(*args, **kwargs) def get_value(self, dictionary): @@ -1767,14 +1768,14 @@ class JSONField(Field): data = data.decode() return json.loads(data) else: - json.dumps(data) + json.dumps(data, cls=self.encoder) except (TypeError, ValueError): self.fail('invalid') return data def to_representation(self, value): if self.binary: - value = json.dumps(value) + value = json.dumps(value, cls=self.encoder) value = value.encode() return value diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index b23389c56..857d3ed94 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -1232,6 +1232,11 @@ class ModelSerializer(Serializer): # `allow_blank` is only valid for textual fields. field_kwargs.pop('allow_blank', None) + if postgres_fields and isinstance(model_field, postgres_fields.JSONField): + # Populate the `encoder` argument of `JSONField` instances generated + # for the PostgreSQL specific `JSONField`. + field_kwargs['encoder'] = getattr(model_field, 'encoder', None) + if postgres_fields and isinstance(model_field, postgres_fields.ArrayField): # Populate the `child` argument on `ListField` instances generated # for the PostgreSQL specific `ArrayField`. diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py index 413d7885d..88c6785b2 100644 --- a/tests/test_model_serializer.py +++ b/tests/test_model_serializer.py @@ -13,6 +13,7 @@ from collections import OrderedDict import django import pytest from django.core.exceptions import ImproperlyConfigured +from django.core.serializers.json import DjangoJSONEncoder from django.core.validators import ( MaxValueValidator, MinLengthValidator, MinValueValidator ) @@ -452,15 +453,17 @@ class TestPosgresFieldsMapping(TestCase): def test_json_field(self): class JSONFieldModel(models.Model): json_field = postgres_fields.JSONField() + json_field_with_encoder = postgres_fields.JSONField(encoder=DjangoJSONEncoder) class TestSerializer(serializers.ModelSerializer): class Meta: model = JSONFieldModel - fields = ['json_field'] + fields = ['json_field', 'json_field_with_encoder'] expected = dedent(""" TestSerializer(): - json_field = JSONField(style={'base_template': 'textarea.html'}) + json_field = JSONField(encoder=None, style={'base_template': 'textarea.html'}) + json_field_with_encoder = JSONField(encoder=, style={'base_template': 'textarea.html'}) """) self.assertEqual(repr(TestSerializer()), expected)