Opened 4 years ago
Closed 4 years ago
#32483 closed Bug (fixed)
QuerySet.values()/values_list() with JSONField returns integers instead of booleans on SQLite.
Reported by: | Matthew Cornell | Owned by: | Mariusz Felisiak |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | 3.1 |
Severity: | Normal | Keywords: | jsonfield querying sqlite |
Cc: | sage | Triage Stage: | Ready for checkin |
Has patch: | yes | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description (last modified by )
I have a model with a JSONField:
class PredictionData(models.Model): data = models.JSONField()
One of the rows contains this dict: {'value': True
}.
I'm querying the model's JSON using 'data__value'
:
PredictionData.objects.values_list('data', 'data__value')
I get correct results for postgres (a boolean) but incorrect for sqlite3 (an int). For this query, sqlite3 wrongly returns:
({'value': True}, 1)
whereas postgres correctly returns
({'value': True}, True)
Same behavior with False/0.
versions:
Python 3.9.1
sqlite3.sqlite_version # '3.33.0'
django.version # '3.1.7'
Change History (14)
comment:1 by , 4 years ago
Triage Stage: | Unreviewed → Accepted |
---|
comment:2 by , 4 years ago
Description: | modified (diff) |
---|
comment:3 by , 4 years ago
Cc: | added |
---|---|
Severity: | Normal → Release blocker |
Summary: | querying JSONField in sqlite returns ints for booleans → QuerySet.values()/values_list() with JSONField returns integers instead of booleans on SQLite. |
Regression tests:
diff --git a/tests/model_fields/test_jsonfield.py b/tests/model_fields/test_jsonfield.py index 89b78de708..43ca00a4d1 100644 --- a/tests/model_fields/test_jsonfield.py +++ b/tests/model_fields/test_jsonfield.py @@ -804,6 +804,16 @@ class TestQuerying(TestCase): with self.subTest(lookup=lookup): self.assertEqual(qs.values_list(lookup, flat=True).get(), expected) + def test_key_values_boolean(self): + qs = NullableJSONModel.objects.filter(value__h=True) + tests = [ + ('value__h', True), + ('value__i', False), + ] + for lookup, expected in tests: + with self.subTest(lookup=lookup): + self.assertIs(qs.values_list(lookup, flat=True).get(), expected) + @skipUnlessDBFeature('supports_json_field_contains') def test_key_contains(self): self.assertIs(NullableJSONModel.objects.filter(value__foo__contains='ar').exists(), False)
comment:4 by , 4 years ago
Component: | Database layer (models, ORM) → Documentation |
---|---|
Severity: | Release blocker → Normal |
It seems there is not much we can do, the SQL query is correct. Unfortunately JSON_EXTRACT()
returns integers for boolean values and it's a documented behavior:
The json_extract(X,P1,P2,...) extracts and returns one or more values from the well-formed JSON at X. If only a single path P1 is provided, then the SQL datatype of the result is NULL for a JSON null, INTEGER or REAL for a JSON numeric value, an INTEGER zero for a JSON false value, an INTEGER one for a JSON true value, ...
There is no way to recognize that we should automatically cast a value to boolean, you can use Cast('json_field__is_true', models.BooleanField())
.
Changing to a documentation issue.
comment:5 by , 4 years ago
OK, there is a way with using JSON_TYPE()
, however it's quite complicated (see the draft changeset) and model_fields.test_jsonfield.TestQuerying.test_isnull_key_or_none
doesn't work with this change. I would prefer to document this caveat in Django < 4.0 and keep this ticket as a cleanup/optimization.
comment:6 by , 4 years ago
Thank you for looking into this Rohith and Mariusz. For now I will avoid the query feature and just pull the data out once Django has deserialized the field.
comment:7 by , 4 years ago
Component: | Documentation → Database layer (models, ORM) |
---|---|
Owner: | changed from | to
Status: | new → assigned |
comment:12 by , 4 years ago
Triage Stage: | Accepted → Ready for checkin |
---|
I was able to reproduce the issue: