In my model I have the following field -
data_fields = ArrayField(models.JSONField(null=True, blank=True), blank=True, null=True)
In order to test this out, I entered the following in the data_field in the django admin -
[{"key": "name", "label": "Name"}, {"key": "amount", "label": "Amount"}]
But I get an error saying
Item 1 in the array did not validate: Enter a valid JSON.
Item 2 in the array did not validate: Enter a valid JSON.
Item 3 in the array did not validate: Enter a valid JSON.
Item 4 in the array did not validate: Enter a valid JSON.
I only get this error if there are multiple key-value pairs in the JSON. If there is only one JSON element with one key-value pair, it works. If there are multiple elements in the array, or there are multiple key-value pairs in the JSON field, I get the error above.
Why is this happening, and how do I fix it?
This is one of the many reasons not to use an ArrayField
or JSONField
in the first place [django-antipatterns]. ArrayField
s are, as far as I know, not very well supported in databases nor in Django, and that results in all sorts of querying problems, and problems with forms. Here for example it will split on a comma, so the first item looks like {"key": "name"
, that is indeed not valid JSON, the proof of this behavior is that it sees four elements, not two. It thus does not know when to split, and as a result passes incorrect items to the JSON deserializer.
That being said, why not just use a JSONField
[Django-doc], after all, a list is a JSON blob, so using:
data_fields = models.JSONField(null=True, blank=True, default=list)
But still, if the structure is fixed, like the JSON blob seems to be, I would advise using an inline:
class Parent(models.Model):
pass
class ParentDataField(models.Model):
parent = models.ForeignKey(
Parent, related_name='data_fields', on_delete=models.CASCADE
)
key = models.CharField(max_length=128)
label = models.CharField(max_length=128)
in fact, we even can ensure that for the same parent
, the key
only occurs at most once:
class Parent(models.Model):
pass
class ParentDataField(models.Model):
parent = models.ForeignKey(
Parent, related_name='data_fields', on_delete=models.CASCADE
)
key = models.CharField(max_length=128)
label = models.CharField(max_length=128)
class Meta:
constraints = [
models.UniqueConstraint(
fields=('parent', 'key'), name='unique_key_per_parent'
)
]
and then work with an inline:
from django.contrib import admin
class ParentDataFieldInline(admin.TabularInline):
model = ParentDataField
class ParentAdmin(admin.ModelAdmin):
inlines = [
ParentDataFieldInline,
]