Skip to content

Commit

Permalink
Fix field value deserialization
Browse files Browse the repository at this point in the history
  • Loading branch information
rpkilby committed Feb 15, 2020
1 parent 8242a92 commit 9261399
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 25 deletions.
15 changes: 2 additions & 13 deletions jsonfield/fields.py
Expand Up @@ -7,6 +7,7 @@

from . import forms
from .encoder import JSONEncoder
from .json import checked_loads

DEFAULT_DUMP_KWARGS = {
'cls': JSONEncoder,
Expand Down Expand Up @@ -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."))

Expand All @@ -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
Expand Down
23 changes: 11 additions & 12 deletions jsonfield/forms.py
Expand Up @@ -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):
Expand All @@ -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)
22 changes: 22 additions & 0 deletions 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

0 comments on commit 9261399

Please sign in to comment.