diff --git a/jsonfield/fields.py b/jsonfield/fields.py index 009251c..55f5c45 100644 --- a/jsonfield/fields.py +++ b/jsonfield/fields.py @@ -7,6 +7,7 @@ from . import forms from .encoder import JSONEncoder +from .json import checked_loads DEFAULT_DUMP_KWARGS = { 'cls': JSONEncoder, @@ -36,14 +37,8 @@ def deconstruct(self): return name, path, args, kwargs def to_python(self, value): - if self.null and value is None: - return None - - if not isinstance(value, (str, bytes, bytearray)): - return value - try: - return json.loads(value, **self.load_kwargs) + return checked_loads(value, **self.load_kwargs) except ValueError: raise ValidationError(_("Enter valid JSON.")) @@ -58,12 +53,6 @@ def get_prep_value(self, value): return None return json.dumps(value, **self.dump_kwargs) - def value_from_object(self, obj): - value = super(JSONFieldMixin, self).value_from_object(obj) - if self.null and value is None: - return None - return json.dumps(value, **self.dump_kwargs) - def formfield(self, **kwargs): if 'form_class' not in kwargs: kwargs['form_class'] = self.form_class diff --git a/jsonfield/forms.py b/jsonfield/forms.py index fe9c403..ab30b63 100644 --- a/jsonfield/forms.py +++ b/jsonfield/forms.py @@ -3,6 +3,8 @@ from django.forms import ValidationError, fields from django.utils.translation import gettext_lazy as _ +from .json import checked_loads + class JSONField(fields.CharField): def __init__(self, *args, dump_kwargs=None, load_kwargs=None, **kwargs): @@ -12,19 +14,16 @@ def __init__(self, *args, dump_kwargs=None, load_kwargs=None, **kwargs): super().__init__(*args, **kwargs) def to_python(self, value): - if isinstance(value, str) and value: - try: - return json.loads(value, **self.load_kwargs) - except ValueError: - raise ValidationError(_("Enter valid JSON.")) - return value - - def clean(self, value): - if not value and not self.required: + if self.disabled: + return value + + if value in self.empty_values: return None - # Trap cleaning errors & bubble them up as JSON errors try: - return super().clean(value) - except TypeError: + return checked_loads(value, **self.load_kwargs) + except json.JSONDecodeError: raise ValidationError(_("Enter valid JSON.")) + + def prepare_value(self, value): + return json.dumps(value, **self.dump_kwargs) diff --git a/jsonfield/json.py b/jsonfield/json.py new file mode 100644 index 0000000..86f1736 --- /dev/null +++ b/jsonfield/json.py @@ -0,0 +1,22 @@ +import json + + +class JSONString(str): + pass + + +def checked_loads(value, **kwargs): + """ + Ensure that values aren't loaded twice, resulting in an encoding error. + + Loaded strings are wrapped in JSONString, as it is otherwise not possible + to differentiate between a loaded and unloaded string. + """ + if isinstance(value, (list, dict, int, float, JSONString, type(None))): + return value + + value = json.loads(value, **kwargs) + if isinstance(value, str): + value = JSONString(value) + + return value